【万字】使用Typescript讲解23种设计模式(附代码)

533 阅读14分钟

本文代码实现已经上传github,感兴趣可以下载学习design-pattern-in-typescript

在阅读本文的同时也可以参考目前github上给出的一些语言的实现,通过比较阅读能够更好地理解精华: PHPJavaPythonJSTS

1. 准备工作

1.1 设计原则六大原则

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

主要遵循面向对象设计原则。

  • 对接口编程而不是对实现编程。

  • 优先使用对象组合而不是继承。

SOLID设计模式6大原则

单一职责原则也称:合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)

迪米特原则也称:最小知识原则(Principle of Least Knowledge,PLK)

1.2 设计模式分类

23种设计模式分类

  • 5种创建型:创建对象,隐藏实现。
  • 7种结构型:关注类和对象的组合。
  • 11种行为型:关注对象之间的通信

1.3 搭建简单工程环境

  • TS+Jest

安装yarn

npm install -g yarn

安装typescript和jest

yarn add -D jest typescript ts-jest @types/jest

初始化ts配置

tsc --init

初始化jest配置

yarn ts-jest config:init
// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
};

增加代码和测试

src/add.ts

const sum = (a: number, b: number): number => {
  return a + b;
}

export default sum;

test/add.test.ts


import add from "../src/add";

describe("add function", () => {
    it("1+1", () => {
        expect(add(1, 1)).toEqual(2);
    })
})

增加格式化配置

.vscode

{
    "editor.formatOnPaste": true
}

增加脚本命令 package.json

{
  "scripts": {
    "test": "yarn jest"
  },
}

运行测试

yarn test

image.png

1.4 UML中几种箭头的含义

三角形,泛化、实现

  • 泛化:类的继承,△ + 实线
  • 实现:类实现接口,△ + 虚线

箭头,依赖、关联

  • 依赖:一个类的功能需要另外一个类支持,虚线箭头
  • 关联:一个类的全局变量引用了另一个类,实线箭头

菱形,聚合,组合

  • 聚合:整体与部分的关系,has-a,空心菱形 + 箭头
  • 组合:整体与部分的关系,contains-a,实心菱形 + 箭头

2. 设计模式

2.1 单例

单例模式:单例模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其实例的方法。

应用场景:

  • I/O 与数据库的连接。
  • 唯一的序列号。

优缺点:

  • 优点:类的实例化限制为一个对象。当仅需要一个对象来协调整个系统的操作时,这非常有用。
  • 缺点:引入了一个全局状态,并且在一个地方更改它可能会影响其他地方,并且它可能变得非常难以调试。

懒汉式

image.png

src/Singleton/Singleton.ts

class Singleton {
  // 构造器私有化(防止new)
  private static instance: Singleton;

  private constructor(){}

  public static getInstance(){
    if(!Singleton.instance) {
      Singleton.instance = new Singleton()
    }

    return Singleton.instance
  }
}

export default Singleton;

src/Singleton/Singleton.test.ts

import Singleton from "../src/Singleton";

describe("Singleton", () => {
    it("instance is single", () => {
        const a = Singleton.getInstance();
        const b = Singleton.getInstance();
        expect(a).toEqual(b);
    })
})

饿汉式 VS 懒汉式

  • 饿汉式(静态常量)
    • 优点:
      • 类装载的时候就完成了实例化
      • 简单
    • 缺点:
      • 没有懒加载
      • 没有使用的话是一种资源浪费
  • 懒汉式
    • 优点:
      • 懒加载
    • 缺点:
      • 只能单线程,不然同时执行,则因为异步导致初始化两个实例

new对象的单例

src/Singleton/Singleton2.ts

class Singleton {
  private static instance: Singleton;

  constructor(){
    if (Singleton.instance === null) {
      Singleton.instance = this;
    }
    return Singleton.instance
  }

  public static getInstance(){
    return new Singleton()
  }
}

export default Singleton;

src/Singleton/Singleton2.test.ts

import Singleton from "../../src/Singleton/Singleton2";

describe("Singleton 2", () => {
    it("instance is single", () => {
        const a = new Singleton();
        const b = new Singleton();
        expect(a).toEqual(b);
    })
});

其他Java的方式也可以参考下:synchronized、枚举、静态内部类(延迟加载)等等。

2.2 建造者

使用多个简单的对象一步一步构建成一个复杂的对象。

应用场景:

  • 对象需要多个步骤创建。
  • 对象有多个形态。

优缺点:

  • 建造者模式的优点:
    • 封装性好,创建和使用分离;
    • 扩展性好,建造类之间独立、一定程度上解耦。
  • 建造者模式的缺点:
    • 产生多余的Builder对象;
    • 产品内部发生变化,建造者都要修改,成本较大。

image.png

由四个类组成:

  • 产品(Product):要创建的产品对象。
  • 抽象建造者(Builder):建造者的抽象类,规范产品对象的各个组成部分的建造。
  • 具体建造者(ConcreteBuilder):具体的 Builder 类,根据不同的业务逻辑,具体到各个对象的各个组成部分的建造。
  • 调用者(Director):调用具体的建造者来创建各个对象的各个部分。

产品:src/Builder/Computer.ts

import SystemInfo from './SystemInfo';

/**
 * 电脑产品
 */
class Computer {
  private systemInfo: SystemInfo = <SystemInfo>{}

  constructor(_: SystemInfo) {
    this.systemInfo = _;
  }

  public get MainBoard(): string {
    return this.systemInfo.MainBoard
  };

  public set MainBoard(_: string) {
    this.systemInfo.MainBoard = _
  };

  public get CPU(): string {
    return this.systemInfo.CPU
  };

  public set CPU(_: string) {
    this.systemInfo.CPU = _
  };

  public get Memory(): string {
    return this.systemInfo.Memory
  };

  public set Memory(_: string) {
    this.systemInfo.Memory = _
  };

  public get HardDisk(): string {
    return this.systemInfo.HardDisk
  };

  public set HardDisk(_: string) {
    this.systemInfo.HardDisk = _
  };

  public get VideoCard(): string {
    return this.systemInfo.VideoCard
  };

  public set VideoCard(_: string) {
    this.systemInfo.VideoCard = _
  };

  public getSystemInfo(): string {
    return `
      主板类型:${this.systemInfo.MainBoard}
      CPU类型:${this.systemInfo.CPU}
      内存类型:${this.systemInfo.Memory}
      硬盘类型:${this.systemInfo.HardDisk}
      显卡类型:${this.systemInfo.VideoCard}
    `
  }

}

export default Computer;

抽象建造者:src/Builder/Builder.ts

import Computer from './Computer';
import SystemInfo from './SystemInfo';

/** 构造器接口 */
abstract class Builder {
  protected systemInfo: SystemInfo = <SystemInfo>{}

  build(): Computer {
    return new Computer(this.systemInfo)
  }

  /** 将CPU安装到主板上 */
  public abstract setupCPU(): void;
  /** 将硬盘连接到主板上 */
  public abstract setupHardDisk(): void;
  /** 将主板固定在机箱中 */
  public abstract setupMainBoard(): void;
  /** 将内存安装到主板上 */
  public abstract setupMemory(): void;
  /** 将显卡安装到主板上 */
  public abstract setupVideoCard(): void;
}

export default Builder;

具体建造者:src/Builder/PersonalsystemInfoBuilder.ts

import Builder from './Builder';

class PersonalsystemInfoBuilder extends Builder {
  public setupCPU(): void {
    this.systemInfo.CPU = '我的CPU';
  }

  public setupHardDisk(): void {
    this.systemInfo.HardDisk = '我的硬盘';
  }

  public setupMainBoard(): void {
    this.systemInfo.MainBoard = '我的主板';
  }

  public setupMemory(): void {
    this.systemInfo.Memory = '我的内存';
  }

  public setupVideoCard(): void {
    this.systemInfo.VideoCard = '我的显卡';
  }

}

export default PersonalsystemInfoBuilder;

具体建造者:src/Builder/OfficeComputerBuilder.ts

import Builder from './Builder';

class OfficeComputerBuilder extends Builder {
  public setupCPU(): void {
    this.systemInfo.CPU = '公司的CPU';
  }

  public setupHardDisk(): void {
    this.systemInfo.HardDisk = '公司的硬盘';
  }

  public setupMainBoard(): void {
    this.systemInfo.MainBoard = '公司的主板';
  }

  public setupMemory(): void {
    this.systemInfo.Memory = '公司的内存';
  }

  public setupVideoCard(): void {
    this.systemInfo.VideoCard = '公司的显卡';
  }

}

export default OfficeComputerBuilder;

调用者:src/Builder/ComputerFactory.ts

import Builder from "./Builder";
import Computer from "./Computer";

/**
 * 电脑工厂
 */
class ComputerFactory {
  /**
   * 封装组装过程
   * @param builder 
   */
  public buildComputer(builder: Builder): Computer {
    builder.setupMainBoard();
    builder.setupCPU();
    builder.setupHardDisk();
    builder.setupMemory();
    builder.setupVideoCard();
    return builder.build()
  }

}

export default ComputerFactory;

测试: test/Builder.test.ts

import ComputerFactory from '../src/Builder/ComputerFactory';
import PersonalComputerBuilder from '../src/Builder/PersonalComputerBuilder';
import OfficeComputerBuilder from '../src/Builder/OfficeComputerBuilder';

const factory = new ComputerFactory();
const personalBuilder = new PersonalComputerBuilder();
const officeBuilder = new OfficeComputerBuilder();

const personalComputer = factory.buildComputer(personalBuilder)
const officeComputer = factory.buildComputer(officeBuilder);

describe("buider", () => {
    it("build pc cumputer", () => {
        expect(personalComputer.getSystemInfo()).toContain('我的显卡');
        expect(officeComputer.getSystemInfo()).toContain('公司的显卡');
    })
});

2.3 原型

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

应用场景:

  • 运行期创建对象。
  • 细胞分裂。

优缺点:

  • 优点:
    • 1、性能提高。
    • 2、逃避构造函数的约束。
  • 缺点:
    • 需要实现clone方法,已有类的改造成本较大。

下面看如何实现 image.png

src/Prototype/Shape.ts

interface Shape {
  [attr: string]: number | any;

  type: string;
  getType(): string
  clone(): object

}

export default Shape;

src/Prototype/Rectangle.ts

import Shape from './Shape';

type Attrs = {
  x: number;
  y: number;
  width: number;
  height: number;
};

class Rectangle implements Shape {
  readonly type: string = 'Rectangle';

  private _attrs: Attrs = <Attrs>{};

  public get x(): number {
    return this._attrs.x;
  }

  public set x(_: number) {
    this._attrs.x = _;
  }

  public get y(): number {
    return this._attrs.y;
  }

  public set y(_: number) {
    this._attrs.y = _;
  }

  public get width(): number {
    return this._attrs.width;
  }

  public set width(_: number) {
    this._attrs.width = _;
  }

  public get height(): number {
    return this._attrs.height;
  }

  public set height(_: number) {
    this._attrs.height = _;
  }

  constructor(obj: Attrs) {
    this._attrs.x = obj.x
    this._attrs.y = obj.y
    this._attrs.width = obj.width
    this._attrs.height = obj.height
  }

  public getType(): string {
    return this.type
  }

  public clone() {
    return new Rectangle(this)
  }
}

export default Rectangle;

src/Prototype/Circle.ts

import Shape from './Shape';

type Attrs = {
  x: number;
  y: number;
  radius: number;
};

class Circle implements Shape {
  readonly type: string = 'Circle';

  private _attrs: Attrs = <Attrs>{}

  constructor(obj: Attrs) {
    this._attrs = {
      x: obj.x,
      y: obj.y,
      radius: obj.radius,
    };
  }

  public get x(): number {
    return this._attrs.x;
  }

  public set x(_: number) {
    this._attrs.x = _;
  }

  public get y(): number {
    return this._attrs.y;
  }

  public set y(_: number) {
    this._attrs.y = _;
  }

  public get radius(): number {
    return this._attrs.radius;
  }

  public set radius(_: number) {
    this._attrs.radius = _;
  }

  public getType(): string {
    return this.type
  }

  public clone() {
    return new Circle(this)
  }
}

export default Circle;

src/Prototype/Square.ts

import Shape from './Shape';

type Attrs = {
  x: number;
  y: number;
  side: number;
};

class Square implements Shape {
  readonly type: string = 'Square';

  private _attrs: Attrs = <Attrs>{}

  constructor(obj: Attrs) {
    this._attrs.x = obj.x
    this._attrs.y = obj.y
    this._attrs.side = obj.side;
  }

  public get x(): number {
    return this._attrs.x;
  }

  public set x(_: number) {
    this._attrs.x = _;
  }

  public get y(): number {
    return this._attrs.y;
  }

  public set y(_: number) {
    this._attrs.y = _;
  }

  public get side(): number {
    return this._attrs.side;
  }

  public set side(_: number) {
    this._attrs.side = _;
  }

  public getType(): string {
    return this.type
  }

  public clone() {
    return new Square(this)
  }
}

export default Square;

src/Prototype/Circle.ts

src/Prototype/ShapeCache.ts

import Shape from './Shape';

class ShapeCache {
  private cache: Map<string, Shape> = new Map();

  public put(type: string, shape: Shape): void {
    this.cache.set(type, shape)
  }

  public get(type: string): Shape | undefined {
    return this.cache.get(type);
  }

}

export default ShapeCache;

test/Prototype.test.ts

import ShapeCache from '../src/Prototype/ShapeCache';
import Circle from '../src/Prototype/Circle';
import Square from '../src/Prototype/Square';
import Rectangle from '../src/Prototype/Rectangle';

const cache = new ShapeCache();
const circle = new Circle({ x: 0, y: 0, radius: 10 });
const square = new Square({ x: 0, y: 0, side: 10 });
const rectangle = new Rectangle({ x: 0, y: 0, height: 10, width: 20 });

cache.put('circle', circle);
cache.put('square', square);
cache.put('rectangle', rectangle);

describe("prototype", () => {
    it("cache shape", () => {
        expect(cache.get('circle')?.radius).toEqual(circle.radius);
        expect(cache.get('square')?.side).toEqual(square.side);
        expect(cache.get('rectangle')?.width).toEqual(rectangle.width);
    })
});

2.4 工厂

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。解决接口选择的问题。

应用场景:Hibernate 换数据库只需换方言和驱动就可以。也就是有多种方案可以选择的地方都可以使用。

优缺点:

  • 优点: 
    • 1、一个调用者想创建一个对象,只要知道其名称就可以了。
    • 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
    • 3、屏蔽产品的具体实现,调用者只关心产品的接口。
  • 缺点:
    • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

image.png

src/Factory/Product.ts

export interface Product {
  name: string;
  price: number;
};

export class ProductA implements Product {
  name: string;
  price: number;

  constructor() {
    this.name = 'ProductA';
    this.price = 10;
  }

}

export class ProductB implements Product {
  name: string;
  price: number;

  constructor() {
    this.name = 'ProductB';
    this.price = 100;
  }
}

src/Factory/Factory.ts

import { Product, ProductB, ProductA } from './Product';

class Factory {
  private store: Record<string, { new(): Product }> = {};// 注意:这里的构造器签名

  constructor() {
    this.store = {
      ProductB,
      ProductA
    }
  }

  public create(type: string): null | Product {
    const Constructor = this.store[type];

    if (!Constructor) return null;

    return new Constructor()
  }
}

export default Factory;

test/Factory.test.ts

import Factory from '../src/Factory/Factory';

const factory = new Factory();

describe("prototype", () => {
    it("cache shape", () => {
        expect(factory.create('ProductA')?.name).toEqual('ProductA');
        expect(factory.create('ProductB')?.name).toEqual('ProductB');
    })
});

2.5 抽象工厂

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

应用场景:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。自己的衣服套装冬装、春装。

抽象工厂模式包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

优缺点:

  • 优点:

    • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
  • 缺点:

    • 产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

image.png

src/AbstractFactory/Product.ts

export interface Pet {
  name: string;
  speak(): string;
};

export interface Food {
  name: string;
};

export class Cat implements Pet {
  name: string = 'cat';
  speak(): string {
    return 'miao~'
  }
}

export class Dog implements Pet {
  name: string = 'dog';
  speak(): string {
    return 'wang~'
  }
}

export class Fish implements Food {
  name: string = 'fish';
}

export class Bone implements Food {
  name: string = 'bone';
}

src/AbstractFactory/Factory.ts

import { Pet, Food, Dog, Cat, Fish, Bone } from './Product';

/**
 * 抽象工厂,可以生成宠物和宠物食品两种产品
 */
export interface PetFactory {
  pet(): Pet

  food(): Food
}

/**
 * 猫商品(猫 + 猫粮)工厂
 */
export class CatFactory implements PetFactory {
  pet(): Pet {
    return new Cat()
  }

  food(): Food {
    return new Fish()
  }
}

/**
 * 狗商品(狗 + 狗粮)工厂
 */
export class DogFactory implements PetFactory {
  pet(): Pet {
    return new Dog()
  }

  food(): Food {
    return new Bone()
  }
}

test/AbstractFactory.test.ts

import { DogFactory, CatFactory } from '../src/AbstractFactory/Factory';

const item1 = new DogFactory()
const item2 = new CatFactory()

describe("abstract factory", () => {
    it("buy pet", () => {
        expect(item1.pet()?.name).toEqual('dog');
        expect(item1.food()?.name).toEqual('bone');

        expect(item2.pet()?.name).toEqual('cat');
        expect(item2.food()?.name).toEqual('fish');
    })
});

2.6 适配器

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。

应用场景:数据线万能接口。

包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

优缺点:

  • 优点:

    • 1、可以让任何两个没有关联的类一起运行。
    • 2、提高了类的复用。
    • 3、增加了类的透明度。
    • 4、灵活性好。
  • 缺点:

    • 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构

image.png

src/Adapter/Adapter.ts

export class Adaptee {
  speak() {
    return 'speak sth'
  }
}

export class Target {
  bark() {
    return 'wang wang'
  }
}

export class Adapter implements Target {
  private adaptee: Adaptee;// 适配者

  constructor(_: Adaptee) {
    this.adaptee = _
  }

  bark(): string {
    return this.adaptee.speak()
  }

}

test/Adapter.test.ts

import { Adapter, Adaptee } from '../src/Adapter/Adapter';

describe("adapter", () => {
    it("adapter bark for speak", () => {
        const obj = new Adapter(new Adaptee());
        expect(obj.bark()).toContain('speak');
    })
});

2.7 桥接

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。

应用场景:换肤

  • 优缺点
    • 优点:
      • 1、抽象和实现的分离。
      • 2、优秀的扩展能力。
      • 3、实现细节对客户透明。
    • 缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

image.png

src/Bridge/Theme.ts

export interface Theme {
  getColor(): string
}

export class DarkTheme implements Theme {
  public getColor(): string {
    return 'Dark Black';
  }
}

export class LightTheme implements Theme {
  public getColor(): string {
    return 'Off white';
  }
}

src/Bridge/Page.ts

import { Theme } from './Theme';

export interface WebPage {
  theme: Theme;
  getContent(): string
}

export class AboutPage implements WebPage {
  theme: Theme;

  constructor(_: Theme) {
    this.theme = _;
  }

  public getContent() {
    return "About page in " + this.theme.getColor();
  }
}

export class DetialPage implements WebPage {
  theme: Theme;

  constructor(_: Theme) {
    this.theme = _;
  }

  public getContent() {
    return "Detial page in " + this.theme.getColor();
  }
}

test/Bridge.test.ts

import { DarkTheme } from '../src/Bridge/Theme';
import { AboutPage, DetialPage } from '../src/Bridge/Page';

describe("brider", () => {
    it("page with dark theme", () => {
        const theme = new DarkTheme();
        const about = new AboutPage(theme);
        const detail = new DetialPage(theme);

        expect(about.getContent()).toContain('Dark Black');
        expect(detail.getContent()).toContain('Dark Black');
    })
});

2.8 装饰器

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装。

应用场景:1、扩展一个类的功能。 2、动态增加功能,动态撤销。如,Java的IO类。

  • 优缺点
    • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

    • 缺点:多层装饰比较复杂。

image.png

src/Decorater/Coffee.ts

/**
 * 饮料
 */
export interface Beverage {
  desc(): String;
  cost(): number
}

/**
 * 意大利浓缩咖啡
 */
export class Espresso implements Beverage {
  desc(): string {
    return '意大利浓缩咖啡';
  }

  cost(): number {
    return 1.99;
  }
}

/**
 * 综合咖啡
 */
export class HouseBlend implements Beverage {
  desc(): string {
    return '综合咖啡';
  }

  cost() {
    return 0.89;
  }
}

/**
 * 调味咖啡
 */
export class CondimentDecorator implements Beverage {
  beverage: Beverage
  times: number = 1;
  type: string = '';

  constructor(_: Beverage) {
    this.beverage = _;
  }

  desc(): string {
    return [this.beverage.desc(), this.type].join(', ');
  }

  cost(): number {
    return this.times * this.beverage.cost();
  }
}

/**
 * 牛奶
 */
export class Milk extends CondimentDecorator {

  constructor(_: Beverage) {
    super(_);
    this.times = 1.8;
    this.type = 'milk'
  }

}

/**
 * 巧克力
 */
export class Chocolate extends CondimentDecorator {

  constructor(_: Beverage) {
    super(_);
    this.times = 1.2;
    this.type = 'chocolate'
  }

}

test/Decorater.test.ts

import { Espresso, Milk, Chocolate } from '../src/Decorater/Coffee';

const mocha = new Chocolate(new Milk(new Espresso()))

describe("decorator", () => {
    it("mocha coffe", () => {
        const desc = mocha.desc()
        expect(desc).toContain('milk');
        expect(desc).toContain('chocolate');
    })
});

2.9 外观

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。

应用场景:JAVA 的三层开发模式。开关机

  • 优缺点
    • 优点:
      • 1、减少系统相互依赖。
      • 2、提高灵活性。
      • 3、提高了安全性。
    • 缺点:
      • 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

image.png

src/Facade/Computer.ts

export class Computer {
  public getElectricShock(): string {
    return "Ouch!";
  }

  public makeSound(): string {
    return "Beep beep!";
  }

  public showLoadingScreen(): string {
    return "Loading..";
  }

  public bam(): string {
    return "Ready to be used!";
  }

  public closeEverything(): string {
    return "Bup bup buzzz!";
  }

  public sooth(): string {
    return "Zzzzz";
  }

  public pullCurrent(): string {
    return "Haah!";
  }
}

export class ComputerFacade {
  private computer;

  constructor(_: Computer) {
    this.computer = _;
  }

  public turnOn(): string {
    return [
      this.computer.getElectricShock(),
      this.computer.makeSound(),
      this.computer.showLoadingScreen(),
      this.computer.bam()
    ].join(' ')
  }

  public turnOff() {
    return [
      this.computer.closeEverything(),
      this.computer.pullCurrent(),
      this.computer.sooth()
    ].join(' ')
  }
}

test/Facade.test.ts

import { ComputerFacade, Computer } from '../src/Facade/Computer';

const computer = new ComputerFacade(new Computer())

describe("facade", () => {
    it("computer turn on/off", () => {
        expect(computer.turnOn()).toEqual('Ouch! Beep beep! Loading.. Ready to be used!');
        expect(computer.turnOff()).toEqual('Bup bup buzzz! Haah! Zzzzz');
    })
});

2.10 享元

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。

应用场景:1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。

  • 优缺点
    • 优点:
      • 大大减少对象的创建,降低系统的内存,使效率提高。
    • 缺点:
      • 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

image.png

src/Flyweight/index.ts

/**
 * 端口类Port
 */
export class Port {
  private _port: string;

  constructor(_: string) {
    this._port = _;
  }

  public set port(_: string) {
    this._port = _;
  }

  public get port(): string {
    return this._port;
  }
}

/**
 * 抽象设备,依赖外部状态
 */
export abstract class NetworkDevice {
  type!: string;
  id: string;
  port!: Port;

  constructor(_: string) {
    this.id = _;
  }

  public getId(): string {
    return this.id;
  }

  // 设置端口
  public assignPort(_: Port) {
    this.port = _
  }
}

/**
 * 交换机
 */
class Switch extends NetworkDevice {
  type: string = 'Switch'
}

/**
 * 集线器
 */
class Hub extends NetworkDevice {
  type: string = 'Hub'
}

/**
 * 享元工厂
 */
export class DeviceFactory {
  private devices: Record<string, NetworkDevice> = {};
  private _terminalNum: number = 0;

  constructor() {
    this.devices = {
      Cisco: new Switch("Cisco-WS-C2950-24"),
      TP: new Hub("TP-LINK-HF8M")
    }
  }

  public getDevice(type: string): NetworkDevice {
    const device = this.devices[type];

    if (device) {
      this._terminalNum++;
    }

    return device
  }

  public get deviceSize() {
    return Object.keys(this.devices).length;
  }

  public get terminalNum(): number {
    return this._terminalNum;
  }
}

test/Flyweight.test.ts

import { DeviceFactory, Port } from '../src/Flyweight';

describe("flyweight", () => {
    it("5 teminals share 2 decices", () => {
        const factory = new DeviceFactory();
        const device1 = factory.getDevice('Cisco');
        const device2 = factory.getDevice('Cisco');
        const device3 = factory.getDevice('Cisco');
        const device4 = factory.getDevice('TP');
        const device5 = factory.getDevice('TP');

        device1.assignPort(new Port("1001"));
        device2.assignPort(new Port("1002"));
        device3.assignPort(new Port("1003"));
        device4.assignPort(new Port("1004"));
        device5.assignPort(new Port("1005"));

        expect(factory.terminalNum).toEqual(5);
        expect(factory.deviceSize).toEqual(2);// 共享2个设备
    })
});

2.11 代理

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。

应用场景:1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

  • 优缺点
    • 优点:
      • 1、职责清晰。
      • 2、高扩展性。
      • 3、智能化。
    • 缺点:
      • 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
      • 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

image.png

src/Proxy/index.ts

export interface Downloader {
  login(): boolean;
  transfer(name: string): string
  downMovie(name: string): string;
}

export class ForeignWebsite implements Downloader {
  private url: string = 'https://www.foreign.wbsite';
  private source: Record<string, string> = {};

  constructor(url: string) {
    if (url = this.url) {
      console.log(`visit ${url}`);
    }

    this.source = {
      '变形金刚': '变形金刚.mp4',
      '阿凡达': '阿凡达.mp4'
    }
  }

  public login(): boolean {
    return true
  }

  public transfer(name: string): string {
    return this.source[name]
  }

  public downMovie(name: string): string {
    // 联网
    this.login()
    // 下载
    return this.transfer(name)
  }
}

export class InlandWebsite implements Downloader {
  private url: string = 'https://www.inland.wbsite';
  private foreign!: ForeignWebsite;
  private cache: Record<string, string> = {};

  constructor(url: string) {
    if (url = this.url) {
      console.log(`visit ${url}`);
    }
  }

  public login(): boolean {
    return this.foreign.login();
  }

  public transfer(name: string): string {
    return this.foreign.transfer(name);
  }

  public connect(url: string) {
    if (url === 'https://www.foreign.wbsite') {
      this.foreign = new ForeignWebsite(url);
    }
  }

  public downMovie(name: string): string {
    // 国内缓存
    if (this.cache[name]) return this.cache[name];

    // 连接外网
    this.connect('https://www.foreign.wbsite');

    if (!this.foreign) {
      throw Error('the foreign websit disconnected!')
    }

    // 登录外网
    this.login();

    // 外网下载
    const source = this.transfer(name)
    this.cache[name] = "国内镜像版本 - " + source;

    return this.cache[name];
  }
}

test/Proxy.test.ts

import { InlandWebsite } from '../src/Proxy';

describe("proxy", () => {
    it("download foreign movie from inland website", () => {
        const site = new InlandWebsite('https://www.inland.wbsite');
        expect(site.downMovie('阿凡达')).toContain('国内镜像版本 - 阿凡达.mp4');
    })
});

2.12 组合

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。

应用场景:树形结构,例如页面组件构建。

  • 优缺点
    • 优点:
      • 1、高层模块调用简单。
      • 2、节点自由增加。
    • 缺点:
      • 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

image.png

src/Composite/index.ts

export abstract class Employee {
  protected salary: number;
  protected name: string;
  protected roles: string[] = [];

  constructor(name: string, salary: number) {
    this.name = name;
    this.salary = salary;
  }

  public getName(): string {
    return this.name;
  }

  public setSalary(salary: number) {
    this.salary = salary;
  }

  public getSalary(): number {
    return this.salary;
  }

  public getRoles(): string[] {
    return this.roles;
  }
}

export class Developer extends Employee {
  public job: string = 'Developer';
}

export class Designer extends Employee {
  public job: string = 'Designer';
}

export class Organization {
  protected employees: Employee[] = [];

  public addEmployee(employee: Employee) {
    this.employees.push(employee);
  }

  public getNetSalaries(): number {
    let netSalary = 0;

    this.employees.forEach(employee => {
      netSalary += employee.getSalary();
    });

    return netSalary;
  }
}

test/Composite.test.ts

import { Developer, Designer, Organization } from '../src/Composite';

describe("composite", () => {
    it("count salery", () => {
        const john = new Developer('Bob', 12000);
        const jane = new Designer('Jim', 15000);

        const organization = new Organization();
        organization.addEmployee(john);
        organization.addEmployee(jane);

        expect(organization.getNetSalaries()).toEqual(27000);

    })
});

2.13 责任链

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。

应用场景:树形结构,例如页面组件构建。

  • 优缺点
    • 优点:
      • 1、降低耦合度。它将请求的发送者和接收者解耦。
      • 2、简化了对象。使得对象不需要知道链的结构。
      • 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
      • 4、增加新的请求处理类很方便。
    • 缺点:
      • 1、不能保证请求一定被接收。
      • 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
      • 3、可能不容易观察运行时的特征,有碍于除错。

image.png

src/ChainOfResponsibilities/index.ts

export abstract class Account {
  protected type!: string;
  protected next!: Account;
  protected balance!: number;

  constructor(balance: number) {
    this.balance = balance;
  }

  public setNext(account: Account) {
    this.next = account;
  }

  public pay(amountToPay: number): string {
    if (this.canPay(amountToPay)) {
      return 'pay by ' + this.type;
    } else if (this.next) {
      return this.next.pay(amountToPay);
    } else {
      throw new Error('None of the accounts have enough balance');
    }
  }

  public canPay(amount: number): boolean {
    return this.balance >= amount;
  }
}

export class Bank extends Account {
  protected type: string = 'bank';
}

export class Paypal extends Account {
  protected type: string = 'paypal';
}

export class Bitcoin extends Account {
  protected type: string = 'bitcoin';
}

test/ChainOfResponsibilities.test.ts

import { Bank, Paypal, Bitcoin } from '../src/ChainOfResponsibilities';

const bank = new Bank(100);
const paypal = new Paypal(200);
const bitcoin = new Bitcoin(300);

// 组装职责链
bank.setNext(paypal);
paypal.setNext(bitcoin);

describe("chain of responsibilities", () => {
    it("choose payment", () => {
        expect(bank.pay(259)).toContain('pay by bitcoin');
    })
});

2.14 命令

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

应用场景:"记录、撤销/重做、事务"

  • 优缺点
    • 优点:
      • 1、降低了系统耦合度。
      • 2、新的命令可以很容易添加到系统中去。
    • 缺点:
      • 1、使用命令模式可能会导致某些系统有过多的具体命令类。

image.png

src/Command/index.ts

// Receiver
export class Bulb {
  public turnOn(): string {
    return "Bulb has been lit";
  }

  public turnOff(): string {
    return "Darkness";
  }
}

export interface Command {
  execute(): string;
  undo(): string;
  redo(): string;
}

// Command
export class TurnOn implements Command {
  protected bulb: Bulb;

  constructor(bulb: Bulb) {
    this.bulb = bulb;
  }

  public execute(): string {
    return this.bulb.turnOn();
  }

  public undo(): string {
    return this.bulb.turnOff();
  }

  public redo(): string {
    return this.execute();
  }
}

export class TurnOff implements Command {
  protected bulb: Bulb;

  constructor(bulb: Bulb) {
    this.bulb = bulb;
  }

  public execute(): string {
    return this.bulb.turnOff();
  }

  public undo(): string {
    return this.bulb.turnOn();
  }

  public redo(): string {
    return this.execute();
  }
}

// Invoker
export class RemoteControl {
  public submit(command: Command): string {
    return command.execute();
  }
}

test/Command.test.ts

import { Bulb, TurnOn, TurnOff, RemoteControl } from '../src/Command';

describe("command", () => {
    it("turn on/off bulb", () => {
        const bulb = new Bulb();

        const turnOn = new TurnOn(bulb);
        const turnOff = new TurnOff(bulb);

        const remote = new RemoteControl();
        expect(remote.submit(turnOn)).toEqual('Bulb has been lit');
        expect(remote.submit(turnOff)).toEqual('Darkness');

    })
});

2.15 迭代器

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

应用场景:遍历一个聚合对象。把在元素之间游走的责任交给迭代器,而不是聚合对象。

  • 优缺点
    • 优点:
      • 1、它支持以不同的方式遍历一个聚合对象。
      • 2、迭代器简化了聚合类。
      • 3、在同一个聚合上可以有多个遍历。
      • 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
    • 缺点:
      • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

image.png

src/Iterator/index.ts

export interface Iterator {
  next(): any;
  hasNext(): boolean;
}

export class ConcreteIterator implements Iterator {
  private collection: any[] = [];
  private position: number = 0;

  constructor(collection: any[]) {
    this.collection = collection;
  }

  public next(): any {
    // Error handling is left out
    var result = this.collection[this.position];
    this.position += 1;
    return result;
  }

  public hasNext(): boolean {
    return this.position < this.collection.length;
  }
}

export interface Aggregator {
  createIterator(): Iterator;
}

export class Numbers implements Aggregator {
  private collection: number[] = [];

  constructor(collection: number[]) {
    this.collection = collection;
  }

  public createIterator(): Iterator {
    return new ConcreteIterator(this.collection);
  }
}

test/Interator.test.ts

import { Numbers, ConcreteIterator } from '../src/Iterator';

describe("iterator", () => {
    it("sum collection", () => {
        const numbers: Numbers = new Numbers([1, 7, 21, 657, 3, 2, 765, 13, 65])
        const it: ConcreteIterator = <ConcreteIterator>numbers.createIterator();

        let sum = 0;
        while (it.hasNext()) sum += it.next()

        expect(sum).toEqual(1534);
    })
});

2.16 中介者

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。

应用场景:对象与对象之间存在大量的关联关系。MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

  • 优缺点
    • 优点:
      • 1、降低了类的复杂度,将一对多转化成了一对一。
      • 2、各个类之间的解耦。
      • 3、符合迪米特原则。
    • 缺点:
      • 中介者会庞大,变得复杂难以维护。

image.png

src/Mediator/index.ts

export interface Mediator {
  showMessage(user: User, time: string, message: string): string
}

// Mediator
export class ChatRoom implements Mediator {
  public showMessage(user: User, time: string, message: string): string {
    const sender = user.getName();
    return `[${new Date(time).toISOString()}, ${sender}]: ${message}`;
  }
}

export class User {
  protected name: string;
  protected room!: ChatRoom;

  public constructor(name: string) {
    this.name = name;
  }

  public getName(): string {
    return this.name;
  }

  public join(room: Mediator): void {
    this.room = room;
  }

  public send(time: string, message: string): string {
    // 通过中介发送消息
    return this.room.showMessage(this, time, message);
  }
}

test/Mediator.test.ts

import { User, ChatRoom } from '../src/Mediator';

describe("mediator", () => {
    it("chatroom", () => {
        const room = new ChatRoom()
        const bob = new User('bob');
        const john = new User('john');

        bob.join(room);
        john.join(room);

        expect(bob.send('2022-11-27T00:00:00.000Z', 'Hi there!')).toEqual('[2022-11-27T00:00:00.000Z, bob]: Hi there!');
        expect(john.send('2022-11-27T00:01:00.000Z', 'Hey!')).toEqual('[2022-11-27T00:01:00.000Z, john]: Hey!');
    })
});

2.17 备忘录

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。

应用场景:打游戏时的存档。 Windows 里的 ctrl + z。数据库的事务管理。

  • 优缺点
    • 优点:
      • 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
      • 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
    • 缺点:
      • 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

image.png

src/Memento/index.ts

export class EditorMemento {
  protected content: string;

  public constructor(content: string) {
    this.content = content;
  }

  public getContent() {
    return this.content;
  }
}

export class Editor {
  protected content = '';

  public type(words: string) {
    this.content += words;
  }

  public getContent(): string {
    return this.content;
  }

  public save(): EditorMemento {
    return new EditorMemento(this.content);
  }

  public restore(memento: EditorMemento) {
    this.content = memento.getContent();
  }
}

test/Memento.test.ts

import { Editor, EditorMemento } from '../src/Memento';

describe("mediator", () => {
    it("chatroom", () => {
        const editor = new Editor();

        // Type some stuff
        editor.type('I have a dream. ');
        // Save the state to restore
        const saved: EditorMemento = editor.save();
        // Type some more
        editor.type('We all a family!');
        // Output: Content before Saving
        expect(editor.getContent()).toEqual('I have a dream. We all a family!');

        // Restoring to last saved state
        editor.restore(saved);
        expect(editor.getContent()).toEqual('I have a dream. ');
    })
});

2.18 观察者

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。

应用场景:事件。一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

  • 优缺点
    • 优点:
      • 1、观察者和被观察者是抽象耦合的。
      • 2、建立一套触发机制。
    • 缺点:
      • 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
      • 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
      • 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

image.png

src/Observer/index.ts

type Event = Function & {
  origin?: Function
}

export class EventEmitter {
  _maxListeners: number = 10;
  _events: Record<string, Event[]> = {};

  /**
   * add listener
   */
  public addListener(type: string, listener: Event) {
    if (!this._events[type]) {
      this._events[type] = [listener];
      return;
    }

    this._events[type].push(listener);
  }

  /**
   * remove listener
   */
  public removeListener(type: string, listener: Event) {
    if (!this._events[type]) return;

    if (!listener) {
      delete this._events[type];
    }

    this._events[type] = this._events[type].filter(e => e !== listener && e.origin !== listener)
  }

  /**
   * do once
   */
  public once(type: string, listener: Event): void {
    const only = (...args: any[]) => {
      listener.apply(this, args);
      this.removeListener(type, listener);
    }

    only.origin = listener;
    this.addListener(type, only);

  }

  /**
   * 执行某类事件
   */
  public emit(type: string, ...args: any[]): void {
    if (!this._events[type]) return;
    this._events[type].forEach(fn => {
      fn.apply(this, args);
    });
  }

  public setMaxListeners(count: number): void {
    this._maxListeners = count
  }
}

test/Observer.test.ts

import { EventEmitter } from '../src/Observer';

describe("observer", () => {
    const emitter = new EventEmitter();
    let ret = ''
    const onceListener = (args = '') => {
        ret += 'onceListener, args:' + args + '.';
    }

    const listener = (args = '') => {
        ret += 'listener, args:' + args + '.';
    }

    it("param", () => {
        // 参数
        emitter.once('click', onceListener);
        emitter.addListener('click', listener);
        emitter.emit('click', '参数');
        expect(ret).toEqual('onceListener, args:参数.listener, args:参数.');
    })

    it("once", () => {
        // 只执行一次
        ret = '';
        emitter.emit('click');
        expect(ret).toEqual('listener, args:.');
    })

    it("remove", () => {
        // 删除事件
        ret = '';
        emitter.removeListener('click', listener);
        emitter.emit('click');
        expect(ret).toEqual('');
    })
});

2.19 访问者

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。

应用场景:需要对一个对象结构中的对象进行很多不同的并且不相关的操作。

  • 优缺点
    • 优点:
      • 1、符合单一职责原则。
      • 2、优秀的扩展性。
      • 3、灵活性。
    • 缺点:
      • 1、具体元素对访问者公布细节,违反了迪米特原则。
      • 2、具体元素变更比较困难。
      • 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

image.png

src/Vistor/index.ts

export interface Visitor {
  visit(engineer: Engineer | Manager): string;
}

export interface Employee {
  accept(visitor: Visitor): string
}

export class Engineer implements Employee {

  private name: string;

  private kpi: number;

  constructor(name: string, kpi: number) {
    this.name = name;
    this.kpi = kpi;
  }

  public getName(): string {
    return this.name;
  }

  public getKpi(): number {
    return this.kpi;
  }

  public accept(visitor: Visitor): string {
    return visitor.visit(this);
  }

  public getCodeLineTotal(): number {
    return this.kpi;
  }
}

export class Manager implements Employee {
  private name: string;

  private kpi: number;

  constructor(name: string, kpi: number) {
    this.name = name;
    this.kpi = kpi;
  }

  public getName(): string {
    return this.name;
  }

  public getKpi(): number {
    return this.kpi;
  }

  public accept(visitor: Visitor): string {
    return visitor.visit(this);
  }

  public getProductNum(): number {
    return this.kpi;
  }
}

export class CEO implements Visitor {

  public visit(role: Manager | Engineer): string {
    if (role instanceof Manager) {
      return "经理:" + role.getName() + "KPI:" + role.getKpi() + " 今年共完成项目:" + role.getProductNum() + "个;"
    }

    return "工程师:" + role.getName() + "KPI:" + role.getKpi() + ';'
  }

}

export class CTO implements Visitor {
  public visit(role: Manager | Engineer): string {
    if (role instanceof Manager) {
      return "经理:" + role.getName() + " 今年共完成项目:" + role.getProductNum() + "个;"
    }

    return "工程师:" + role.getName() + " 今年代码量" + role.getCodeLineTotal() + "行;"
  }
}

export class Organization {

  private list: Employee[] = []

  public addEmployee(employee: Employee): Organization {
    this.list.push(employee);
    return this;
  }

  public report(visitor: Visitor): string {
    let report = ''
    this.list.forEach(employee => {
      report += employee.accept(visitor);
    });

    return report
  }
}

test/Vistor.test.ts

import { Engineer, Manager, Organization, CTO, CEO } from '../src/Visitor';

describe("visitor", () => {
    it("report for cto/ceo", () => {
        const a = new Engineer("小张", 1200);
        const b = new Engineer("小王", 2300);
        const c = new Engineer("小李", 3400);

        const A = new Manager("张总", 2);
        const B = new Manager("王总", 4);
        const C = new Manager("李总", 6);

        const org = new Organization();
        org.addEmployee(a)
            .addEmployee(b)
            .addEmployee(c)
            .addEmployee(A)
            .addEmployee(B)
            .addEmployee(C);

        expect(org.report(new CTO())).toEqual('工程师:小张 今年代码量1200行;工程师:小王 今年代码量2300行;工程师:小李 今年代码量3400行;经理:张总 今年共完成项目:2个;经理:王总 今年共完成项目:4个;经理:李总 今年共完成项目:6个;');
        expect(org.report(new CEO())).toEqual('工程师:小张KPI:1200;工程师:小王KPI:2300;工程师:小李KPI:3400;经理:张总KPI:2 今年共完成项目:2个;经理:王总KPI:4 今年共完成项目:4个;经理:李总KPI:6 今年共完成项目:6个;');
    })
});

2.20 策略

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。

应用场景:旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。

  • 优缺点
    • 优点:
      • 1、算法可以自由切换。
      • 2、避免使用多重条件判断。
      • 3、扩展性良好。
    • 缺点:
      • 1、策略类会增多。
      • 2、所有策略类都需要对外暴露。

image.png

src/Strategy/index.ts

export interface Sort {
  data: number[];
  sort(data: number[]): string;
}

export class QuickSort implements Sort {
  public data: number[] = [];
  public sort(data: number[]): string {
    this.data = data;
    return 'sort by quick sort';
  }
}

export class InsertSort implements Sort {
  public data: number[] = [];
  public sort(data: number[]): string {
    this.data = data;
    return 'sort by insert sort';
  }
}

export class BubbleSort implements Sort {
  public data: number[] = [];
  public sort(data: number[]): string {
    this.data = data;
    return 'sort by bubble sort';
  }
}

export class Context {
  private method: Sort;

  constructor(method: Sort) {
    this.method = method;
  }

  public sort(data: number[]): string {
    return this.method.sort(data);
  }
}

test/Strategy.test.ts

import { Context, BubbleSort, QuickSort, InsertSort } from '../src/Strategy';

describe("strategy", () => {
    it("report for cto/ceo", () => {
        const data = [1, 5, 4, 3, 2, 8];

        const bubble = new Context(new BubbleSort());
        expect(bubble.sort(data)).toEqual('sort by bubble sort');

        const insert = new Context(new InsertSort());
        expect(insert.sort(data)).toEqual('sort by insert sort');

        const quick = new Context(new QuickSort());
        expect(quick.sort(data)).toEqual('sort by quick sort');
    })
});

2.21 状态

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

应用场景:代码中包含大量与对象状态有关的条件语句。

  • 优缺点
    • 优点:
      • 1、封装了转换规则。
      • 2、枚举可能的状态,在枚举状态之前需要确定状态种类。
      • 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
      • 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
      • 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
    • 缺点:
      • 1、状态模式的使用必然会增加系统类和对象的个数。
      • 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
      • 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

image.png

src/State/index.ts

export interface State {
  doAction(context: Logistics): string;
}

export class Logistics {
  public state!: State;

  updateState(state: State) {
    this.state = state;
  }

  doAction(): string {
    return this.state.doAction(this);
  }
}

export class OrderState implements State {
  public doAction(context: Logistics): string {
    return "商家已经接单,正在处理中..."
  }
}

export class ProductOutState implements State {
  public doAction(context: Logistics): string {
    return "商品已经出库..."
  }
}

export class TransportState implements State {
  public doAction(context: Logistics): string {
    return "商品正在运输中..."
  }
}

test/State.test.ts

import { Logistics, OrderState, TransportState, ProductOutState } from '../src/State';

describe("state", () => {
    it("change logistics state", () => {
        const ctx = new Logistics();
        const order = new OrderState();
        const productOut = new ProductOutState();
        const transport = new TransportState();

        ctx.updateState(order);
        expect(ctx.doAction()).toEqual('商家已经接单,正在处理中...');

        ctx.updateState(productOut);
        expect(ctx.doAction()).toEqual('商品已经出库...');

        ctx.updateState(transport);
        expect(ctx.doAction()).toEqual('商品正在运输中...');
    })
});

2.22 模板

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

应用场景:spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码

  • 优缺点
    • 优点:
      • 1、封装不变部分,扩展可变部分。
      • 2、提取公共代码,便于维护。
      • 3、行为由父类控制,子类实现。
    • 缺点:
      • 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

image.png

src/TemplateMethod/index.ts

export abstract class Builder {
  public build(): string {
    let ret = ''
    ret += this.test();
    ret += this.lint();
    ret += this.assemble();
    ret += this.deploy();
    return ret;
  }

  public abstract test(): string;
  public abstract lint(): string;
  public abstract assemble(): string;
  public abstract deploy(): string;
}

export class IosBuilder extends Builder {
  public test(): string {
    return 'Running ios tests';
  }

  public lint(): string {
    return 'Linting the ios code';
  }

  public assemble(): string {
    return 'Assembling the ios build';
  }

  public deploy(): string {
    return 'Deploying ios build to server';
  }
}

test/TemplateMethod.test.ts

import { IosBuilder } from '../src/TemplateMethod';

describe("template method", () => {
    it("ios builder", () => {
        const builder = new IosBuilder()
        expect(builder.build()).toEqual('Running ios testsLinting the ios codeAssembling the ios buildDeploying ios build to server');
    })
});

2.23 解释器

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。

应用场景:SQL 解析、符号处理引擎。

  • 优缺点
    • 优点:
      • 1、可扩展性比较好,灵活。
      • 2、增加了新的解释表达式的方式。
      • 3、易于实现简单文法。
    • 缺点:
      • 1、可利用场景比较少。
      • 2、对于复杂的文法比较难维护。
      • 3、解释器模式会引起类膨胀。
      • 4、解释器模式采用递归调用方法。

image.png

src/Interpreter/index.ts

export interface Expression {
  interpret(context: String): boolean;
}

export class TerminalExpression implements Expression {

  private data: string;

  constructor(data: string) {
    this.data = data;
  }

  public interpret(context: string): boolean {
    return context.includes(this.data)
  }
}

export class OrExpression implements Expression {

  private expr1!: Expression;
  private expr2!: Expression;

  constructor(expr1: Expression, expr2: Expression) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }

  public interpret(context: string): boolean {
    return this.expr1.interpret(context) || this.expr2.interpret(context);
  }
}

export class AndExpression implements Expression {

  private expr1!: Expression;
  private expr2!: Expression;

  constructor(expr1: Expression, expr2: Expression) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }

  public interpret(context: string): boolean {
    return this.expr1.interpret(context) && this.expr2.interpret(context);
  }
}

test/Interpreter.test.ts

import { TerminalExpression, OrExpression, AndExpression } from '../src/Interpreter';

describe("interpreter", () => {
    it("expression", () => {
        const robert = new TerminalExpression("Robert");
        const john = new TerminalExpression("John");
        const or = new OrExpression(robert, john);

        const julie = new TerminalExpression("Julie");
        const married = new TerminalExpression("Married");
        const and = new AndExpression(julie, married);

        expect(or).toBeTruthy();
        expect(and).toBeTruthy();
    })
});