装饰器模式
在程序开发中,许多时候都并不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰者模式。装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象
在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活,还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;另一方面,继承这种功能复用方式通常被称为“白箱复用”,“白箱”是相对可见性而言的,在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性,使用继承还会带来另外一个问题,在完成一些功能复用的同时,有可能创建出大量的子类,使子类的数量呈爆炸性增长
装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式
例子
1class Plane {2 fire() {3 console.log('发射子弹')4 }5}67class MissileDecorator {8 constructor(plane) {9 this.plane = plane10 }11 fire() {12 this.plane.fire()13 console.log('发射导弹')14 }15}1617const plane = new Plane()18const missileDecorator = new MissileDecorator(plane)19const doubleMissileDecorator = new MissileDecorator(missileDecorator)2021doubleMissileDecorator.fire() // 发射子弹 发射导弹 发射导弹
包装器
从功能上而言,decorator 能很好地描述这个模式,但从结构上看,wrapper 的说法更加贴切。装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会
JavaScript 的装饰者模式
Vue 中的数组的变异方法 push() pop() shift() unshift() splice() sort() reverse()
1const arrMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']2arrMethods.forEach(method => {3 const oldMethod = Array.prototype[method]4 Array.prototype[method] = function (value) {5 console.log('data update') // 增加的“装饰”6 oldMethod.call(this, value)7 }8})
对于对象方法的装饰,我们常会遇到 this 指向的问题
1const oldGetElementById = document.getElementById23document.getElementById = function (id) {4 console.log('ididid')5 return oldGetElementById.apply(this, id) // 如果不加 apply,oldGetElementById 是个全局的函数,this 指向 window6}
AOP 装饰函数
1Function.prototype.before = function (fn) {2 return (...args) => {3 fn(...args) // 注意这里 this 要避免被劫持4 return this(...args)5 }6}78Function.prototype.after = function (fn) {9 return (...args) => {10 const ret = this(...args)11 fn(...args)12 return ret13 }14}
但有时人们并不喜欢这种原型污染,我们用函数实现
1function before(fn, beforeFn) {2 return (...args) => {3 beforeFn(...args)4 return fn(...args)5 }6}78function after(fn, afterFn) {9 return (...args) => {10 const ret = fn(...args)11 afterFn(...args)12 return ret13 }14}
装饰者模式与代理模式
装饰者模式和第6章代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。代理模式和装饰者模式最重要的区别在于它们的意图和设计目的
代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情
装饰者模式的作用就是为对象动态加入行为
代理模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链
装饰者模式是实实在在的为对象增加新的职责和行为,而代理做的事情还是跟本体一样