前言
如果你写过typescript,可能会听过一个词 ——“装饰器”。这个词在java开发中会更加的常见,是通过一种写法来丰富某个类的方法的操作。而装饰器的来源就是装饰者模式,今天我们就来看看什么是装饰者模式。
设计思路
不知道大家有没有玩过在一款叫“飞机大战”的游戏。在游戏中会画面会随机出现一些加强子弹,当接触到子弹之后玩家的飞机攻击就会加强。
这部份的代码如果用面向对象的方式来写就是
class Plane{
// 普通攻击
fire(){
// ...
}
}
class MissileDecorator{
constructor(plane){
this.plane = plane;
}
fire(){
this.plane.fire();
// 导弹攻击
// ...
}
}
const plane = new Plane();
const plane = new MissileDecorator(plane);
plane.fire();
在这里MissileDecorator就是一个“装饰类”,通过MissileDecorator对plane的装饰,攻击就会加强。这种通过嵌套实现代码逻辑逐渐丰富的设计就是装饰者模式。
在JavaScript中实现
上方的实现是一种相对规范的“面向对象”的写法,跟其他设计模式一样,作为一门动态语言。JavaScript在装饰者模式的实现上也可以有自己的便捷写法。
class Plane{
// 普通攻击
fire(){
// ...
}
}
const missileDecorator = function(){
// 导弹攻击
// ...
}
const plane = new Plane();
const fire = plane.fire();
plane.fire = function(){
fire();
missileDecorator();
}
plane.fire();
这种写法就很“JS”,通过一个新的函数直接丰富原来的fire方法。当然我们也可以把这个装饰过程封装成一个函数。
const toMissileDecorator = function(plane){
var _fire = plane.fire();
plane.fire = function(){
_fire();
missileDecorator();
}
}
AOP装饰函数
首先需要声明,AOP装饰函数是一种代码优化写法,它本身跟装饰者模式没有直接关系。但是在这个场景下,它很适合完成装饰者模式,所以在这里稍微提一下。
它的核心思想就是通过before,after方法为原逻辑加入装饰。
class Plane{
constructor(){
this.beforeFns = [];
this.afterFns = [];
}
// 普通攻击
fire(){
// 先执行所有before
for(const fn of this.beforeFns){
fn();
}
// 普通攻击内容...
// 再执行所有after
for(const fn of this.afterFns){
fn();
}
}
// 添加before方法
before(fnc){
this.beforeFns.push(fnc);
return this;
}
// 添加after方法
after(fnc){
this.afterFns.push(fnc);
return this;
}
}
const plane = new Plane();
plane.before(missileDecorator).before(anotherFnc);
plane.fire();
通过这种方式,可以更好地实现装饰者模式,并且可以省略一些装饰类的封装。同时,与装饰函数相比,连续执行before可以更加便捷添加装饰代码。
使用场景
表单校验
在客户端的表单校验中,开发者可能会在表单请求的逻辑中加入表单校验,这样就会使校验的逻辑和请求逻辑耦合在一起。通过装饰者模式可以在请求的基础上加入表单校验的内容,从而使两部分的逻辑解耦。
// 表单校验
const vaildata = function(){
// ...
}
// 加入装饰
form.before(vaildata);
// 提交表单
form.submit();
总结
今天我们了解了装饰者模式,相信有读者会有这样的疑惑。装饰者模式和代理模式有什么区别?这两者确实很像,他们都在执行一段核心逻辑之前,加入一些额外的逻辑。但从设计的角度上理解,两者的差距还是很大的。装饰者模式侧重的是为一个核心功能加以装饰,丰富内容。而代理模式是通过一个代理,在合适的时候访问核心逻辑。两者在设计理念上有着本质区别。
参考
《JavaScript设计模式与开发实践》—— 曾探