在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活, 还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;另一方面,继承这种功能复用方式通常被称为“白箱复用”,“白箱”是相对可见性而言的, 在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式
故事背景
假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第三级时可以发射原子弹。
传统面向对象实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| let Plane = function () {}; Plane.prototype.fire = function () { console.log("发射普通子弹"); };
let MissileDecorator = function (plane) { this.plane = plane; }; MissileDecorator.prototype.fire = function () { this.plane.fire(); console.log("发射导弹"); };
let AtomDecorator = function (plane) { this.plane = plane; }; AtomDecorator.prototype.fire = function () { this.plane.fire(); console.log("发射原子弹"); };
let plane = new Plane(); plane = new MissileDecorator(plane); plane = new AtomDecorator(plane); plane.fire();
|
装饰者对象和它所装饰的对象拥有一致的接口,通过传入被装饰对象,调用其对应方法,并添加职责形成聚合对象。
动态语言实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| let plane = { fire: function () { console.log("发射普通子弹"); }, };
let missileDecorator = function () { console.log("发射导弹"); };
let atomDecorator = function () { console.log("发射原子弹"); };
let fire1 = plane.fire; plane.fire = function () { fire1(); missileDecorator(); };
let fire2 = plane.fire; plane.fire = function () { fire2(); atomDecorator(); }; plane.fire();
|
因为js可以直接修改对象或者对象的某个方法,那么可以通过一个变量a
暂存被装饰函数,然后替换旧方法,在新方法内通过a
调用旧方法。
但是直接替换有this指向问题。
AOP实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Function.prototype.before = function (beforefn) { let _self = this; return function () { beforefn.apply(this, arguments); return _self.apply(this, arguments); }; }; Function.prototype.after = function (afterfn) { let _self = this; return function () { let ret = _self.apply(this, arguments); afterfn.apply(this, arguments); return ret; }; };
|
缺点
- 通过AOP实际上放回的是新函数,在原函数上保存的属性则会丢失
- 叠加了作用域,如果装饰的链条过长,性能上会受到影响
与代理模式的区别
代理模式的目的:当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。
装饰者模式的作用:为对象动态的加入行为。
换句话说,代理模式的关系一开始就可以被确定,而装饰者模式用于一开始不能确定对象的全部功能时
参考
《javascript设计模式与开发实践》