TypeScript 设计模式之装饰模式

129 阅读5分钟

引言

本文介绍设计模式之装饰模式相关内容,包括装饰模式的四大组成部分以及之间的依赖关系;装饰模式的优缺点分析;装饰模式的使用场景,装饰模式实现案例。

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)

需要注意本文采用先上车,再买票的方式组织。先看如何容,在看组成部分。

装饰者模式的例子~两年半的练习生上车

装饰者模式,有点类似于在一个快递盒的外层再包装一层装饰盒子,假如盒子里面有一只猫,每次敲盒子猫都会叫一声。现在我们想这样实现,如果猫放在的是音乐盒里面先 music 5s 猫再叫一声;如果放在的是 Rap 盒子里面先 只因你太美 5s 猫再叫一声。叫这一声只能是 不能是又鸟

实际叫的还是猫,并不是代理模式哈~ 这种情况下我们选择扩展来实现猫的唱跳Rap功能, 如果选择继承,那么势必会新增两个子类来进行扩展,is-a 就不是满足了。我们采用装饰来实现一下:



// 定义一个Cat 接口
interface CatInterface<> {
  miao(): string;
}

// 定义一个狸花猫
class DragonLi implements CatInterface {
  miao<T extends string>(type = 'DragonLi'as T): `${T} miao~` {
    return `${type} miao~`;
  }
}

// 定义 decoration 抽象
abstract class Decoration implements CatInterface {
  protected cat!: CatInterface;
  abstract miao(): string;
  // 才艺表演
  abstract show(): void;
}

// 具体的装饰角色
class MusicBoxDecoration extends Decoration {
  protected cat: CatInterface;

  constructor(cat: CatInterface) {
    super()
    this.cat = cat;
  }
  miao(): string {
    this.show();
    return this.cat.miao();
  }
  show(): void {
    // throw new Error("Method not implemented.");
    console.log('只因你太美~~');
  }

}

// 具体的装饰角色
class RapBoxDecoration extends Decoration {
  protected cat: CatInterface;

  constructor(cat: CatInterface) {
    super();
    this.cat = cat;
  }
  miao(): string {
    this.show();
    return this.cat.miao();
  }
  show(): void {
    // throw new Error("Method not implemented.");
    console.log('rap~ rap~');
  }
}

const liCat = new DragonLi();

const musicBox = new MusicBoxDecoration(liCat);
const rapBox = new RapBoxDecoration(liCat);

console.log(musicBox.miao());

console.log(rapBox.miao());

这就是装饰模式~ 简单吧

装饰者模式包含4个核心的部分

  • 抽象构件,核心需要包装的目标对象的抽象【接口或者是抽象类,如果非要用父类其实也可以】。这里是猫的抽象接口 CatInterface
  • 具体构件,需要包装的目标对象 。这里是 DragonLi 狸花猫对象就是一个具体构件
  • 装饰角色抽象,一般是抽象类,实现抽象构件接口或者扩展的其他接口。属性上存在一个具体构件的属性(靠它来调用基本的逻辑)。
  • 具体的装饰角色,具体实现装饰的实现类。需要把最原始最核心的东西装饰出来,变化顺序、执行其他逻辑等等。这里的 RapBoxDecoration MusicBoxDecoration 就是两个具体的装饰角色。

四个核心组成部分中第三个是可选的,你可以直接一步到位写成具体的装饰角色。但是不建议这样做,OOP 编程思想中 SOLID 法则指明开闭原则:抽象约束(约束扩展的范围和边界),面向接口编程或者面向抽象编程~

装饰模式的优点

一句话就是: 灵活好扩展

  • 装饰类和被装饰的类可以独立发展,而不会耦合。装饰类依赖的是接口,接口不变,装饰类就不会受到影响。
  • 装饰模式是继承关系的一个替代方案,不管装饰多少层最后返回的对象还是基础构件。如果我们将开篇的实现采用继承来实现,分别继承 DragonLi 的子类重写 miao 方法来实现,当Cat存在多个实现类时这个问题变得很困难:布偶猫、英短~~~ 在music盒子里面也是先唱歌再叫一声,区别是叫声可能不同。这时候我们需要再继续对布偶猫、英短猫分别扩展两个子类来实现。优势就明显了起来~当然这是基于场景的【设计模式都是基于场景考虑的,需要根据不同的场景选择不同的设计模式】
  • 装饰模式可以动态的扩展一个实现类的功能。比如猫放在不同的盒子,它有不同的反应,一会儿唱、一会儿跳、一会儿rap~。

装饰模式的缺点

多层的装饰比较复杂,装饰类的装饰类的装饰类~ 俄罗斯套娃。多层的装饰会带来巨大的工作量,设计上避免出现多层的装饰。

这个好避免~

装饰模式的使用场景

  • 需要扩展一个类的功能,或给一个类增加附加功能。
  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  • 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。

总结

装饰模式的核心在于设计好基础构件的类关系,需要区分什么时候这个变化需要扩展到基础构件中去;什么时候只需要扩展一个装饰对象来实现变化;社区里面大家都在说核心构件的接口一但确定就不要修改,但是设计能力,架构能力有时候很难支撑我们一次性设计好。最稳妥的办法就是不断重构你的代码,每次出现新需求的时候都思考清楚对原来代码的冲击和变化,针对不同的场景选择不同的设计模式和实现方式来组织代码。让唱跳rap 更加优雅,更加好维护。下期再见~