This website requires JavaScript.

设计模式—装饰器模式

by  shirmy  

JavaScript 装饰器模式

一、前言

在传统面向对象语言中,如果想为一个对象创建新的功能时,往往采用继承的方式,比如:

class Shape {
  constructor(name) {
    this.name = name
  }

  draw() {
    console.log(`draw ${this.name}`)
  }
}

class ShapeColor extends Shape {
  constructor(name) {
    super(name)
  }

  setColor(color) {
    console.log(`color the ${this.name} ${color}`)
  }
}

const circle = new ShapeColor('triangle')
circle.setColor('blue')
circle.draw()

但是这样的方式会存在超类Shape和子类ShapeColor之间存在强耦合性的问题,一旦超类发生改变,那么子类也会随之发生改变,因此这种方式并不能灵活。紧接上面的例子,如果要设置边的颜色,边的宽度,是否空心等一系列问题时,如果都为他们创建相应的子类,显然是十分繁琐冗余的。

为了解决这些问题,我们需要装饰器模式,装饰器模式属于结构型的设计模式,其的特点是:

  • 不改变对象的基础上
  • 在程序运行期间给对象动态的添加职责

二、JavaScript 中的装饰器模式

为了不改变原有的对象,我们可以把原对象放入到一个新的对象中以形成一个聚合对象。并且这些对象都有相同的接口。当我们使用这个装饰器对象时,会顺着请求链请求到上一个对象。对于用户来说,这个装饰器对象是透明的,用户可以依照这种方式一层一层的递归下去。

class Shape {
  constructor(name) {
    this.name = name
  }

  draw() {
    console.log(`draw ${this.name}`)
  }
}

class ColorDecorator {
  constructor(shape) {
    this.shape = shape
  }

  draw() {
    this.setColor()
    this.shape.draw()
  }

  setColor() {
    console.log(`color the ${this.shape.name}`)
  }
}

let circle = new Shape('circle')
circle.draw()

let decorator = new ColorDecorator(circle)
decorator.draw()

如上例所示,通过ColorDecorator对象实现了一个相同的draw()方法,并在其中封装了setColor()这个额外的职责方法,用户同样在调用draw()方法时,也调用了上一个对象Shapedraw()方法。我们可以以此类推,给对象添加上色,设置边框的一系列职责。

实际上,装饰器也是一种包装器,把上个对象包装到某个对象中,层层包装。

三、ES7 中的装饰器模式

在ES7版本中引入了装饰器,但该方法仍未定案,不能贸然使用

装饰类

简单示例

@dec
class Shape { }

function dec(target) {
  target.isShape = true
}

console.log(Shape.isShape)

参数传递

@Dec(true)
class Shape {
  // ...
}

function Dec(isDec) {
  return function(target) {
    target.isDec = isDec
  }
}

console.log(Shape.isDec)

mixin

function mixin(...list) {
  return function(target) {
    Object.assign(target.prototype, ...list)
  }
}

const Color = {
  setColor() {
    console.log('set color')
  }
}

@mixin(Color)
class Shape {}

let shape = new Shape()
shape.setColor()

在上面的示例中,通过Object.assign()setColor()方法添加到Shape中。

装饰方法

class Shape {
  @log
  setColor(color) {
    return color
  }
}

function log(target, name, descriptor) {
  var oldValue = descriptor.value

  descriptor.value = function() {
    console.log(`Calling ${name} width `, arguments)
    return oldValue.apply(this, arguments)
  }

  return descriptor
}

const shape = new Shape()
const color = shape.setColor('red')
console.log('color', color)

上面的示例中,添加了一个log的装饰器,当执行setColor()方法时,会自动打印日志。

四、设计原则

装饰器模式将现有对象和装饰器进行分离,两者独立存在,符合开放封闭原则。

参考

相关推荐
  • 自己写一个Webpack插件
  • Vue双向绑定原理及实现
  • Koa2框架原理及实现
  • 如何优雅地操作DOM
  • 使用Jest对Vue进行自动化测试
  • Jest前端自动化测试框架
  • 使用Nginx配置HTTPS和反向代理
  • 全栈开发—Travis CI持续集成
  • 全栈开发—博客服务端(Koa2)
  • 全栈开发—博客后台管理系统