# 装饰模式

# 参考

https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/decorator.html#id13 (opens new window)

http://c.biancheng.net/view/1366.html (opens new window)

# 定义

在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

# 应用场景

一般有两种方式可以实现给一个类或对象增加行为:

  1. 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。

  2. 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

在以下情况下可以使用装饰模式:

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸 性增长;第二类是因为类定义不能继承(如final类).

# 实现、应用思路

装饰模式主要包含以下角色。

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。

  2. 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。

  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。

  4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

# 优点

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。

  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。

  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。

  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

# 缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。

  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

# 代码实例

// 通过装饰器模式让一个机器人变得更强

class Robot {
  fight() {
    console.log('用拳头攻击')
  }
}

class RobotCanShooting {
  robot: Robot
  constructor(robot) {
    this.robot = robot
  }
  fight() {
    this.robot.fight()
    console.log('用枪攻击')
  }
}

class RobotWithRocket {
  robot: Robot
  constructor(robot) {
    this.robot = robot
  }
  fight() {
    this.robot.fight()
    console.log('用火箭攻击')
  }
}

let robot = new Robot()
console.log('robot开始攻击')
robot.fight()
console.log('---升级---')
// 通过装饰类(包装类)RobotCanShooting和RobotWithRocket的动态修饰,robot的攻击变得更强了
robot = new RobotCanShooting(robot)
robot = new RobotWithRocket(robot)
robot.fight()

/* 作为动态语言,在JavaScript中改变对象的行为是比较简单的,我们可以直接改变对象或者对象的方法,
并不一定需要使用类来实现装饰模式,如下 */

const fightWithGun = () => console.log('用枪攻击')
const fightWithRocket = () => console.log('用火箭攻击')
const robot2 = new Robot()
let { fight } = robot2
fight = () => {
  robot2.fight()
  fightWithGun()
  fightWithRocket()
}
console.log('\nrobot2开始攻击')
fight()

// 如果装饰行为较多,可以通过AOP来实现相对优雅装饰模式
interface Function {
  after(fun: Function): Function
}
Function.prototype.after = function (afterFn) {
  const self = this
  return function () {
    const result = self.apply(this, arguments)
    afterFn.apply(this, arguments)
    return result
  }
}
const robot3 = new Robot()
const filght = robot3.fight.after(fightWithGun).after(fightWithRocket)
console.log('\nrobot3开始攻击')
fight()
上次更新: 5/17/2020, 9:24:33 AM