Skip to content

AHABHGK

装饰器模式

在程序开发中,许多时候都并不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰者模式。装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象

在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活,还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;另一方面,继承这种功能复用方式通常被称为“白箱复用”,“白箱”是相对可见性而言的,在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性,使用继承还会带来另外一个问题,在完成一些功能复用的同时,有可能创建出大量的子类,使子类的数量呈爆炸性增长

装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式

例子

1class Plane {
2 fire() {
3 console.log('发射子弹')
4 }
5}
6
7class MissileDecorator {
8 constructor(plane) {
9 this.plane = plane
10 }
11 fire() {
12 this.plane.fire()
13 console.log('发射导弹')
14 }
15}
16
17const plane = new Plane()
18const missileDecorator = new MissileDecorator(plane)
19const doubleMissileDecorator = new MissileDecorator(missileDecorator)
20
21doubleMissileDecorator.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.getElementById
2
3document.getElementById = function (id) {
4 console.log('ididid')
5 return oldGetElementById.apply(this, id) // 如果不加 apply,oldGetElementById 是个全局的函数,this 指向 window
6}

AOP 装饰函数

1Function.prototype.before = function (fn) {
2 return (...args) => {
3 fn(...args) // 注意这里 this 要避免被劫持
4 return this(...args)
5 }
6}
7
8Function.prototype.after = function (fn) {
9 return (...args) => {
10 const ret = this(...args)
11 fn(...args)
12 return ret
13 }
14}

但有时人们并不喜欢这种原型污染,我们用函数实现

1function before(fn, beforeFn) {
2 return (...args) => {
3 beforeFn(...args)
4 return fn(...args)
5 }
6}
7
8function after(fn, afterFn) {
9 return (...args) => {
10 const ret = fn(...args)
11 afterFn(...args)
12 return ret
13 }
14}

装饰者模式与代理模式

装饰者模式和第6章代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。代理模式和装饰者模式最重要的区别在于它们的意图和设计目的

  • 代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情

  • 装饰者模式的作用就是为对象动态加入行为

代理模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链

装饰者模式是实实在在的为对象增加新的职责和行为,而代理做的事情还是跟本体一样