zhangdizhangdi

装饰器模式 ⭐

定义

装饰器模式(Decorator Pattern),在不修改原对象的情况下,动态地给对象增加功能

符合的设计原则:

  1. 开放关闭(OCP):扩展功能,不改原代码
  2. 单一职责(SRP):每个装饰器只做一件事
  3. 合成复用:用组合代替继承

实现

1. 基于高阶函数

js
// 1. 原本稳如老狗的老业务代码(不要去改它!)
function submitForm() {
  console.log("执行:提交表单核心逻辑...");
}

// 2. 写一个装饰器函数:在执行原函数之前,先执行一段新逻辑
function beforeDecorator(originalFn, beforeFn) {
  return function(...args) {
    beforeFn.apply(this, args); // 先执行增加的功能
    return originalFn.apply(this, args); // 再执行原函数
  }
}

// 3. 增强功能:比如提交前需要做表单校验
function validate() {
  console.log("增强功能:执行复杂的表单校验...");
}

// 4. 把它们组装起来(给裸机套上手机壳)
const enhancedSubmit = beforeDecorator(submitForm, validate);

// 调用增强后的方法
enhancedSubmit(); 
// 打印输出:
// 增强功能:执行复杂的表单校验...
// 执行:提交表单核心逻辑...

2. 基于 @ 语法糖

js
function readonly(target, name, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Person {
  first = ''
  last = ''

  constructor() {
    this.first = 'A'
    this.last = 'B'
  }

  @readonly
  name() {
    return `${this.first} ${this.last}`
  }
}

let p = new Person()
console.log(p.name())
// p.name = function () {
//   console.log(100)
// } // Cannot assign to read only property 'name' of object '#<Person>'
js
function log(target, name, descriptor) {
  console.log(target)
  console.log(name)
  console.log(descriptor)

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

class Math {
  @log
  add(a, b) {
    return a + b
  }
}
let math = new Math()
const result = math.add(4, 6)
console.log(result)

应用场景

  • React HOC
  • Vue composition
  • AOP 面向切面编程,业务和系统基础功能分离(日志、统计、鉴权等)
  • 改变函数的参数
  • 表单验证

vs 继承

继承是“改结构”,装饰器是“加能力”

对比 继承 装饰器
扩展方式 修改类结构 包装对象
灵活性
耦合
是否运行时扩展

参考