9. 装饰器模式(Decorator)

问题:想给对象动态添加功能,而不修改原有类或使用继承。

核心:用包装类包裹原对象,调用时先执行增强逻辑再委托给原对象。

interface Coffee {
  cost(): number;
  desc(): string;
}

class PlainCoffee implements Coffee {
  cost(): number { return 10; }
  desc(): string { return '咖啡'; }
}

class MilkDecorator implements Coffee {
  #coffee: Coffee;
  constructor(coffee: Coffee) { this.#coffee = coffee; }
  cost(): number { return this.#coffee.cost() + 3; }
  desc(): string { return `${this.#coffee.desc()} + 牛奶`; }
}

class SugarDecorator implements Coffee {
  #coffee: Coffee;
  constructor(coffee: Coffee) { this.#coffee = coffee; }
  cost(): number { return this.#coffee.cost() + 1; }
  desc(): string { return `${this.#coffee.desc()} + 糖`; }
}

let myCoffee: Coffee = new PlainCoffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);

console.log(myCoffee.desc()); // 咖啡 + 牛奶 + 糖
console.log(myCoffee.cost()); // 14
package decorator

import "fmt"

type Coffee interface {
  Cost() int
  Desc() string
}

type BaseCoffee struct{}

func (b BaseCoffee) Cost() int    { return 10 }
func (b BaseCoffee) Desc() string { return "咖啡" }

type MilkDecorator struct{ Coffee Coffee }

func (d MilkDecorator) Cost() int    { return d.Coffee.Cost() + 3 }
func (d MilkDecorator) Desc() string { return d.Coffee.Desc() + " + 牛奶" }

type SugarDecorator struct{ Coffee Coffee }

func (d SugarDecorator) Cost() int    { return d.Coffee.Cost() + 1 }
func (d SugarDecorator) Desc() string { return d.Coffee.Desc() + " + 糖" }

// 使用
// coffee := BaseCoffee{}
// coffee = MilkDecorator{Coffee: coffee}
// coffee = SugarDecorator{Coffee: coffee}
// fmt.Println(coffee.Desc()) // 咖啡 + 牛奶 + 糖
// fmt.Println(coffee.Cost()) // 14

实战场景

  • HTTP 中间件增强:给请求处理函数叠加日志、鉴权、限流、缓存、压缩、错误处理等能力。
  • 函数性能计时:包装任意函数,在执行前后记录耗时,不侵入原始业务逻辑。
  • 权限控制:在 service 方法外包一层权限检查,只有通过后才调用原方法。
  • 缓存装饰:对查询函数加缓存,命中时直接返回,未命中时再调用原函数并写入缓存。
  • 重试和熔断:远程调用外层包上重试、超时、降级、熔断逻辑,调用者仍然看到同一个接口。
  • UI 组件增强:在基础输入框外包错误提示、标签、帮助文本、loading 状态或埋点逻辑。
  • 数据流处理:读取文件或网络流时,一层负责解压,一层负责解密,一层负责缓冲,最终仍是同一个读取接口。
  • 埋点增强:给按钮点击、表单提交、路由跳转包一层事件上报,不修改原交互逻辑。
  • 测试探针:测试时给真实对象包一层 spy,记录调用次数和参数,同时继续调用真实实现。
  • 组合式能力开关:同一个核心对象根据配置动态叠加不同能力,比继承出大量子类更灵活。

JS 的高阶函数就是这个思路:fn = log(timing(fn)) 一层层包装。