1 设计模式介绍

设计原则:识别应用中变化的方面,把它们和不变的方面分开。

**把会变化的部分取出来并封装,这样,以后你就可以修改或扩展这个部分,而不会影响其他不需要变化的部分。**这句话几乎是每一个设计模式的基础。

设计原则:针对接口编程,而不是针对实现编程。

设计原则:优先使用组合而不是继承。

interface QuackBehavior {
  quack(): void;
}

class Quack implements QuackBehavior {
  public quack(): void {
    console.log('quack');
  }
}

class MuteQuack implements QuackBehavior {
  public quack(): void {
    console.log('mute');
  }
}

class Squeak implements QuackBehavior {
  public quack(): void {
    console.log('Squeak');
  }
}

class Duck {
  quackBehavior: QuackBehavior;
  public performQuack() {
    this.quackBehavior.quack();
  }
  setQuackBehavior(qb: QuackBehavior) {
    this.quackBehavior = qb;
  }
}

策略模式定义了了一个算法族,分别封装起来,使得它们之间可以互相变换。策略让算法的变化独立于使用它的客户。

设计模式让你和其他开发人员之间有共享的词汇。一旦懂了这些词汇,你可以更容易地和其他开发人员沟通,并激发不同设计模式的开发人员开始学习设计模式。设计模式也能提升你关于架构的思考层次。

设计模式不直接进入你的代码,他们首先进入你的“大脑”。一旦你在大脑中装入管用的模式知识,你就能够开始在新设计中应用它们。当你发现你的旧代码堕落成没有弹性的烂摊子,可以用设计模式改写。

良好的OO设计是可复用、可扩展和可维护的。设计模式依赖于OO基础与原则。

2 观察者模式

报纸的订阅过程和观察者模式非常相似。

2022_07_07_IMG_6212

观察者模式定义对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。

当两个对象之间松耦合时,它们可以交互,但是通常对彼此所知甚少。观察者模式是松耦合的一个很棒的例子。

主题知道观察者的唯一一件事是,它实现了某个接口(观察者接口)。我们可以在任何时候添加新的观察者。要添加新的类型的观察者,我们不需要修改主题。我们可以彼此独立地复用主题或观察者。改变主题或观察者其中一方,不会影响另一方。

设计原则:尽量做到交互的对象之间的松耦合设计。

// 主题
interface Subject {
  registerObserver(o: Observer): void;
  removeObserver(o: Observer): void;
  notifyObservers(): void;
}

// 观察者
interface Observer {
  update(): void;
}

interface DisplayElement {
  display(): void;
}

class WeatherData implements Subject {
  private observers: Observer[];
  private temperature: number;
  private humidity: number;
  private pressure: number;

  constructor() {
    this.observers = [];
  }

  public registerObserver(o: Observer): void {
    this.observers.push(o);
  }

  public removeObserver(o: Observer): void {
    // 删除对象
    // this.observers
  }

  public notifyObservers(): void {
    // 遍历所有观察者,并调用每个观察者的update方法
    this.observers.forEach((item) => {
      item.update();
    });
  }

  public measurementsChanged() {
    this.notifyObservers();
  }

  public setMeasurements(temperature: number, humidity: number, pressure: number) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    this.measurementsChanged();
  }

  public getTemperature(): number {
    return this.temperature;
  }

  public getHumidity(): number {
    return this.humidity;
  }

  public getPressure(): number {
    return this.pressure;
  }
}

class CurrentConditionsDisplay implements Observer, DisplayElement {
  private temperature: number;
  private humidity: number;
  private weatherData: WeatherData;

  constructor(weatherData: WeatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
  }

  public update(): void {
    this.temperature = this.weatherData.getTemperature();
    this.humidity = this.weatherData.getHumidity();
    this.display();
  }

  public display(): void {
    console.log(`温度:${this.temperature},湿度:${this.humidity}`);
  }
}

class ForecastDisplay implements Observer, DisplayElement {
  private weatherData: WeatherData;
  private currentPressure = 29.92;
  private lastPressure: number;

  constructor(weatherData: WeatherData) {
    this.weatherData = weatherData;
    weatherData.registerObserver(this);
  }

  public update(): void {
    this.lastPressure = this.currentPressure;
    this.currentPressure = this.weatherData.getPressure();
    this.display();
  }

  public display(): void {
    console.log(`森林气压:${this.currentPressure}`);
  }
}

class WeatherStation {
  public static main(args: string[]): void {
    const weatherData = new WeatherData();

    new CurrentConditionsDisplay(weatherData);
    new ForecastDisplay(weatherData);

    weatherData.setMeasurements(80, 65, 30.4);
  }
}

WeatherStation.main([]);

3 装饰者模式

设计原则:类应该对扩展开放,但对修改关闭。

2022_07_11_IMG_6216

装饰者模式动态地将额外责任附加到对象上。对于扩展功能,装饰者提供子类化之外的弹性替代方案。

在装饰者模式中,组件是被装饰者装饰的。

// 饮料基类
abstract class Beverage {
  description = 'Unknown Beverage';

  public getDescription() {
    return this.description;
  }

  public abstract cost(): number;
}

// 调料装饰者
abstract class CondimentDecorator extends Beverage {
  beverage: Beverage;
  public abstract getDescription(): string;
}

//  浓缩咖啡饮料
class Espresso extends Beverage {
  constructor() {
    super();
    this.description = 'Espresso';
  }
  cost() {
    return 1.99;
  }
}

class HouseBlend extends Beverage {
  constructor() {
    super();
    this.description = 'House Blend Coffee';
  }

  cost() {
    return 0.89;
  }
}

class DarkRoast extends Beverage {
  constructor() {
    super();
    this.description = 'Dark Roast';
  }
  public cost(): number {
    return 0.99;
  }
}

// 摩卡装饰器
class Mocha extends CondimentDecorator {
  constructor(beverage: Beverage) {
    super();
    this.beverage = beverage;
  }

  public getDescription(): string {
    return this.beverage.getDescription() + ', Mocha';
  }

  public cost(): number {
    return this.beverage.cost() + 0.2;
  }
}

// 豆奶装饰器
class Soy extends CondimentDecorator {
  constructor(beverage: Beverage) {
    super();
    this.beverage = beverage;
  }

  public getDescription(): string {
    return this.beverage.getDescription() + ', Soy';
  }

  public cost(): number {
    return this.beverage.cost() + 0.1;
  }
}

class Whip extends CondimentDecorator {
  constructor(beverage: Beverage) {
    super();
    this.beverage = beverage;
  }

  public getDescription(): string {
    return this.beverage.getDescription() + ', Whip';
  }

  public cost(): number {
    return this.beverage.cost() + 0.1;
  }
}

class StarbuzzCoffee {
  static main(): void {
    const beverage = new Espresso();
    console.log(beverage.getDescription() + '$' + beverage.cost());

    const beverage2 = new Whip(new Mocha(new Mocha(new DarkRoast())));
    console.log(beverage2.getDescription() + '$' + beverage2.cost());

    const beverage3 = new Whip(new Mocha(new Soy(new HouseBlend())));
    console.log(beverage3.getDescription() + '$' + beverage3.cost());
    
    // Espresso$1.99
    // Dark Roast, Mocha, Mocha, Whip$1.49
    // House Blend Coffee, Soy, Mocha, Whip$1.29
  }
}

StarbuzzCoffee.main();
2022_07_12_IMG_62172022_07_12_IMG_6218

装饰者模式的缺点是会造成大量的小类,数量大到给试图使用基于装饰者的API的开发人员带来困扰。

4 工厂模式

工厂处理创建对象的细节。

class SimplePizzaFactory {
  public createPizze(type: string) {
    const pizza: Pizza = null;
    if (type === 'cheese') {
    } else if (type === 'clam') {
    }
    return pizza;
  }
}

class PizzaStore {
  factory: SimplePizzaFactory;
  constructor(factory: SimplePizzaFactory) {
    this.factory = factory;
  }

  orderPizza(type: string) {
    const pizza = this.factory.createPizze(type);

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

    return pizza;
  }
}

工厂方法处理对象的创建,并将对象创建封装在子类中,使得超类中的客户代码从子类的对象创建代码解耦。

abstract factoryMethod(type: string): Product

// 抽象比萨
abstract class Pizza {
  name: string;
  dough: string;
  sauce: string;
  toppings: string[] = [];
  prepare() {
    console.log('Preparing' + this.name);
    console.log('Tossing dough...');
    console.log('Adding sauce...');
    console.log('Adding toppings: ');
    this.toppings.forEach((item) => {
      console.log('  ' + item);
    });
  }
  bake() {
    console.log('Bake for  25 minutes at 350');
  }
  cut() {
    console.log('Cutting the pizza into diagonal slices');
  }
  box() {
    console.log('Place pizza in official PizzaStore box');
  }
  public getName(): string {
    return this.name;
  }
}

// 纽约风味披萨
class NYStyleCheesePizza extends Pizza {
  constructor() {
    super();
    this.name = 'NY style Sauce and Cheese Pizza';
    this.dough = 'Thin Crust Dough';
    this.sauce = 'Marinara Sauce';

    this.toppings.push('Grated Reggiano Cheese');
  }
}

// 超类-比萨店
abstract class PizzaStore {
  orderPizza(type: string) {
    const pizza = this.createPizza(type);

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

    return pizza;
  }
  // 工厂方法,返回特定的披萨
  abstract createPizza(type: string): Pizza;
}

// 子类-纽约风味比萨店
class NYStylePizzaStore extends PizzaStore {
  createPizza(type: string): Pizza {
    if (type === 'cheese') {
      // const pizza = new NYStyleCheesePizza();
    } else if (type === 'clam') {
      // pizza = new NYStyleClamPizza();
    }
    return new NYStyleCheesePizza();
  }
}

// 子类-芝加哥风味比萨店
class ZJGStylePizzaStore extends PizzaStore {
  createPizza(type: string): Pizza {
    // if xxxx
    return new NYStyleCheesePizza();
  }
}

class PizzaTestDrive {
  static main() {
    const nyStore = new NYStylePizzaStore();
    const pizza = nyStore.orderPizza('cheese');
    console.log(pizza);
  }
}

PizzaTestDrive.main();
2022_07_13_IMG_6220

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法让类把实例化推迟到子类。

2022_07_13_IMG_6221

设计原则:依赖抽象,不依赖具体类。

避免OO设计违反依赖倒置原则:

  • 变量不应该持有到具体类的引用。
  • 类不应该派生自具体类。
  • 方法不应该覆盖其任何基类的已实现方法。
// 抽象工厂
interface PizzaIngredientFactory {
  createDough(): Dough;
  createSauce(): Sauce;
  createCheese(): Cheese;
}

// 纽约原材料工厂,实现抽象工厂
class NYPizzaIngredientFactory implements PizzaIngredientFactory {
  createDough() {
    return new TinCrustDough();
  }
  createSauce() {
    return new MarinaraSauce();
  }
  createCheese() {
    return new ReggianoCheese();
  }
}

// 将Pizza的原料准备方法抽象
abstract class Pizza {
  name: string;
  dough: string;
  sauce: string;
  toppings: string[] = [];
  abstract prepare(): void;

  bake() {
    console.log('Bake for 25 minutes at 350');
  }
  cut() {
    console.log('Cutting the pizza into diagonal slices');
  }
  box() {
    console.log('Place pizza in official PizzaStore box');
  }
  public getName(): string {
    return this.name;
  }
}

// 纽约风味披萨实现自己的prepage方法,从原材料工厂中自定义原材料
class NYStyleCheesePizza extends Pizza {
  ingredientFactory: PizzaIngredientFactory;
  constructor(ingredientFactory: PizzaIngredientFactory) {
    super();
    // 材料工厂实例
    this.ingredientFactory = ingredientFactory;
  }

  prepare(): void {
    console.log('preparing' + this.name);
    this.dough = this.ingredientFactory.createDough();
    this.sauce = this.ingredientFactory.createSauce();
    // ...
  }
}

// 纽约风味披萨店
class NYStylePizzaStore extends PizzaStore {
  createPizza(type: string): Pizza {
    const ingredientFactory = new NYPizzaIngredientFactory()
    if (type === 'cheese') {
      // const pizza = new NYStyleCheesePizza(ingredientFactory);
    } else if (type === 'clam') {
      // pizza = new NYStyleClamPizza(ingredientFactory);
    }
    return new NYStyleCheesePizza(ingredientFactory);
  }
}

抽象工厂模式提供一个接口来创建相关或依赖对象的家族,而不需要指定具体类。

2022_07_13_IMG_6279

工厂方法和抽象工厂的区别是抽象工厂把一组相关的产品集结起来,而工厂方法只负责一类产品。

要点:

  • 所有工厂都封装对象的创建。
  • 简单工厂虽然不是真正的设计模式,但依然可用为一个简单的方法,将客户从具体类解耦。
  • 工厂方法靠继承:对象创建被委托给子类,子类实现工厂方法来创建对象。
  • 抽象工厂靠对象组合:对象创建在工厂接口暴露的方法中实现。
  • 所有工厂模式都通过减少应用对具体类的依赖,促进了松耦合。
  • 工厂方法的意图,是允许一个类延迟实例化到其子类。
  • 抽象工厂的意图,是创建相关对象家族,不必依赖于其具体类。
  • 依赖倒置原则指导我们避免依赖具体类型,尽量依赖抽象。
  • 工厂是强有力的技巧,让我们针对抽象编码,而不是针对具体类。

5 单件模式

单件模式确保一个类只有一个实例,并提供了一个全局访问点。

//  类只能被实例化一次
class Singleton {
  private static uniqueInstance: Singleton;
  private constructor() {}

  public static getInstance(): Singleton {
    if (!this.uniqueInstance) {
      this.uniqueInstance = new Singleton();
    }
    return this.uniqueInstance;
  }
}

//  单件模式,第二种方法
class Singleton {
  // 在初始化时创建
  private static uniqueInstance: Singleton = new Singleton();
  private constructor() {}

  public static getInstance(): Singleton {
    return this.uniqueInstance;
  }
}

要点:

  • 单件模式确保应用中一个类最多只有一个实例。
  • 单件模式也提供访问此实例的全局点。
  • Java的单件实现用了一个私有构造器、一个静态方法以及一个静态变量。

6 命令模式

2022_07_17_IMG_6374
interface Command {
  execute(): void;
}

// 接收者
class Light {
  on(): void {
    console.log('灯亮了');
  }
  off(): void {
    console.log('灯关了');
  }
}

// 命令
class LightOnCommand implements Command {
  light: Light;
  constructor(light: Light) {
    this.light = light;
  }
  execute(): void {
    this.light.on();
  }
}

// 接收者
class GarageDoor {
  up(): void {
    console.log('The garageDoor up');
  }
  down(): void {
    console.log('The garageDoor down');
  }
  stop(): void {
    console.log('The garageDoor stop');
  }
  lightOn(): void {
    console.log('The garageDoor lightOn');
  }
  lightOff(): void {
    console.log('The garageDoor lightOff');
  }
}

// 命令
class GarageDoorOpenCommand implements Command {
  garageDoor: GarageDoor;
  constructor(garageDoor: GarageDoor) {
    this.garageDoor = garageDoor;
  }
  execute(): void {
    this.garageDoor.up();
  }
}

// 调用者
class SimpleRemoteControl {
  slot: Command;
  constructor() {}

  setCommand(command: Command) {
    this.slot = command;
  }

  buttonWasPressed() {
    this.slot.execute();
  }
}

// 客户
class RemoteControlTest {
  static main() {
    // 调用者
    const remote = new SimpleRemoteControl();
    // 接收者
    const light = new Light();
    const garageDoor = new GarageDoor();
    // 创建一条命令
    const lightOn = new LightOnCommand(light);
    const garageDoorOpen = new GarageDoorOpenCommand(garageDoor);
    // 把命令传给调用者
    remote.setCommand(lightOn);
    remote.buttonWasPressed();
    remote.setCommand(garageDoorOpen);
    remote.buttonWasPressed();
  }
}
RemoteControlTest.main();

命令模式把请求封装为对象,以便用不同的请求、队列或者日志请求来参数化其他对象,并支持可撤销的操作。

当需要把做出请求的对象从知道如何执行请求的对象解耦时,使用命令模式。

2022_07_17_IMG_6375

命令模式的应用有:日程安排、线程池、日志以及作业队列等等...

要点:

  • 命令模式把做出请求的对象从知道如何执行请求的对象解耦。
  • 命令对象处在解耦的中心,封装接收者以及一个(或一组)动作。
  • 调用者通过调用命令对象的execute()请求,这会使得接收者的动作被调用。
  • 调用者可以用命令参数化,甚至可以在运行时动态地进行。
  • 通过实现一个undo()方法来把对象重建到最后一次执行execute()前的状态,命令可以支持撤销。
  • 宏命令是命令模式的一种简单的延伸。它允许调用多个命令。

7 适配器和外观模式

适配器模式

2022_07_18_IMG_6376

适配器模式将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作。

适配器分为对象适配器和类适配器。区别是对象适配器使用组合implements,而类适配器使用extends继承。

2022_07_18_IMG_6460
interface Duck {
  quack(): void;
  fly(): void;
}

class MallardDuck implements Duck {
  quack(): void {
    console.log('Quack');
  }
  fly(): void {
    console.log("I'm flying");
  }
}

interface Turkey {
  gobble(): void;
  fly(): void;
}

class WildTurkey implements Turkey {
  gobble(): void {
    console.log('Gobble gobble');
  }
fly(): void {
  console.log("i'm flying a short distance");
}
}

// 火鸡适配器
class TurkeyAdapter implements Duck {
  turkey: Turkey;
  constructor(turkey: Turkey) {
    this.turkey = turkey;
  }

  quack(): void {
    this.turkey.gobble();
  }

  fly(): void {
    for (let i = 0; i < 5; i++) {
      this.turkey.fly();
    }
  }
}

// 鸭子适配器
class DuckAdapter implements Turkey {
  duck: Duck;
  constructor(duck: Duck) {
    this.duck = duck;
  }
  gobble(): void {
    this.duck.quack();
  }
  fly(): void {
    this.duck.fly();
  }
}

外观模式

2022_07_19_IMG_6461
// 外观模式,简化接口
class HomeTheateFacade {
  amp: Amplifier;
  tuner: Tuner;
  player: StreamingPlayer;
  projector: Projector;
  lights: TheaterLights;
  screen: Screen;
  popper: PopcomPopper;

  constructor(amp: Amplifier, tuner: Tuner, player: StreamingPlayer, projector: Projector, lights: TheaterLights, screen: Screen, popper: PopcomPopper) {
    this.amp = amp;
    this.tuner = tuner;
    this.player = player;
    this.projector = projector;
    this.lights = lights;
    this.screen = screen;
    this.popper = popper;
  }

  public watchMovie(movie: string) {
    console.log('get ready to watch a movie...');
    this.popper.on();
    this.popper.pop();
    this.lights.dim();
    this.screen.donw();
    // ...
  }

  public endMovie() {
    console.log('shutting movie theater down...');
    this.popper.off();
    this.lights.on();
    this.screen.up();
    // ...
  }
}

外观模式为子系统中的一组接口提供了 一个统一的接口。外观定义了一个更高级别的接口,使得子系统更容易使用。

设计原则

最少知识原则:只和你的密友谈话。

对于任何对象,从该对象的任何方法,只调用属于一下范围的方法:

  • 对象自身。
  • 作为参数传给方法的对象。
  • 该方法创建或实例化的任何对象。
  • 对象的任何组件(实例变量引用的任何对象)。

缺点:运用这个原则会导致需要编写更多的“包装者”类来处理对其他组件的方法调用,造成复杂度和开发时间增加,运行时性能下降。

要点:

  • 当你需要使用一个已有的类,而其接口不符合你的需要,就用适配器。
  • 当你需要简化并统一一个大接口,或者一个复杂的接口集,就用外观。
  • 适配器改变接口以符合客户的期望。
  • 外观将客户从一个复杂子系统解耦。
  • 适配器包装一个对象以改变其接口,装饰者包装一个对象以添加新的行为和责任,而外观“包装”一群对象以简化其接口。

8 模板方法模式

模板方法模式在一个方法中定义一个算法的骨架,而把一些步骤延迟到子类。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

// 咖啡因饮料,将茶和咖啡的共性抽离
abstract class CaffeineBeverage {
  prepareRecipe() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
  }
  abstract brew(): void;
  abstract addCondiments(): void;

  boilWater() {}
  pourInCup() {}
}

class Tea extends CaffeineBeverage {
  brew(): void {
    // 实现茶的brew方法
  }
  addCondiments(): void {
    // 实现茶的addCondiments方法
  }
}

class Coffee extends CaffeineBeverage {
  brew(): void {}
  addCondiments(): void {}
}
2022_07_20_IMG_6475
// 挂钩模板方法
abstract class CaffeineBeverageWithHook {
  prepareRecipe() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    if (this.customerWantsCondiments()) {
      this.addCondiments();
    }
  }
  abstract brew(): void;
  abstract addCondiments(): void;

  boilWater() {}
  pourInCup() {}

  // 可以被子类覆盖
  customerWantsCondiments(): boolean {
    return true;
  }
}

好莱坞原则

不要打电话给(调用)我们,我们会打电话给(调用)你。

2022_07_20_IMG_6476

要点

  • 模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。
  • 模板方法的抽象类可以定义具体方法、抽象方法和钩子。
  • 钩子是一种方法,它在抽象类中不做事或者只做缺省的事情,但子类可以覆盖它。
  • 策略模式和模板方法模式都封装算法,前者通过组合,后者通过继承。
  • 工厂方法是模板方法的一个特例。

9 迭代器和组合模式

迭代器模式

2022_07_21_WechatIMG207

迭代器模式提供了一种方式,可以访问一个聚合对象中的元素而又不暴露其潜在的表示。

// 菜单子元素类
class MenuItem {
  name: string;
  description: string;
  vegetarian: boolean;
  price: number;

  constructor(name: string, description: string, vegetarian: boolean, price: number) {
    this.name = name;
    this.description = description;
    this.vegetarian = vegetarian;
    this.price = price;
  }

  getName() {
    return this.name;
  }

  getDescription() {
    return this.description;
  }

  getVegetarian() {
    return this.vegetarian;
  }

  getPirce() {
    return this.price;
  }
}

// 迭代器接口
interface Iterator {
  hasNext(): boolean;
  next(): MenuItem;
}

// 午餐菜单迭代器
class DnierMenuIterator implements Iterator {
  items: MenuItem[];
  position: number = 0;

  constructor(items: MenuItem[]) {
    this.items = items;
  }

  next(): MenuItem {
    const menuItem = this.items[this.position];
    this.position++;
    return menuItem;
  }

  hasNext(): boolean {
    return this.position <= this.items.length || !!this.items[this.position];
  }
}

// 午餐菜单,实现createIterator创建迭代器接口
class DinerMenu {
  menuItems: MenuItem[];

  constructor() {
    this.menuItems = [];
    this.menuItems.push(new MenuItem('Regular Pancake Breakfast', 'xxxx', false, 2.99));
    this.menuItems.push(new MenuItem('BlueBerry Pancakes', 'Oancakes made with fresh blueberries', true, 3.49));
  }

  createIterator(): Iterator {
    return new DnierMenuIterator(this.menuItems);
  }
}

const iterator = new DinerMenu().createIterator();
while (iterator.hasNext()) {
  console.log(iterator.next());
}

设计原则

一个类应该只有一个变化的原因。

表现为高内聚。

内聚(cohesion),用来度量一个类或模块完成单个目的或者责任时有多紧密。

当一个模块或一个类围绕一组相关功能来设计时,我们说它应具有高内聚;

组合模式

组合模式允许你将对象组合成树形结构来表现部分-整体层次结构。组合让客户可以统一处理个别对象和对象组合。

2022_07_24_IMG_6554
// 抽象组件类,既是菜单也是子菜单
abstract class MenuComponent {
  // 为每个方法定义缺省的实现
  add(menuComponent: MenuComponent): void {
    throw new Error('unSupport operation');
  }
  remove(menuComponent: MenuComponent): void {
    throw new Error('unSupport operation');
  }
  getChild(i: number): MenuComponent {
    throw new Error('unSupport operation');
  }

  getName(): string {
    throw new Error('unSupport operation');
  }
  getDescription(): string {
    throw new Error('unSupport operation');
  }
  getPrice(): number {
    throw new Error('unSupport operation');
  }
  isVegetarian(): boolean {
    throw new Error('unSupport operation');
  }

  print(): void {
    throw new Error('unSupport operation');
  }
}

// 菜单类
class MenuItem extends MenuComponent {
  name: string;
  description: string;
  vegetarian: boolean;
  price: number;

  constructor(name: string, description: string, vegetarian: boolean, price: number) {
    super();
    this.name = name;
    this.description = description;
    this.vegetarian = vegetarian;
    this.price = price;
  }

  getName(): string {
    return this.name;
  }
  getDescription(): string {
    return this.description;
  }
  getPrice(): number {
    return this.price;
  }
  isVegetarian(): boolean {
    return this.vegetarian;
  }

  print(): void {
    console.log(' ' + this.getName());
    if (this.isVegetarian) {
      console.log('(v)');
    }
    console.log(', ' + this.getPrice());
    console.log('    --' + this.getDescription());
  }
}

// 子菜单类
class Menu extends MenuComponent {
  menuComponents: MenuComponent[] = [];
  name: string;
  description: string;
  constructor(name: string, description: string) {
    super();
    this.name = name;
    this.description = description;
  }
  add(menuComponent: MenuComponent): void {
    this.menuComponents.push(menuComponent);
  }
  remove(menuComponent: MenuComponent): void {
    this.menuComponents.splice(
      this.menuComponents.findIndex((item) => item === menuComponent),
      1
    );
  }
  getChild(i: number): MenuComponent {
    return this.menuComponents[i];
  }
  getName(): string {
    return this.name;
  }
  getDescription(): string {
    return this.description;
  }
  print(): void {
    console.log('\n' + this.getName());
    console.log(',  ' + this.getDescription());
    console.log('---------------------');
    this.menuComponents.forEach((item) => {
      item.print();
    });
  }
}

class Waitress {
  allMenus: MenuComponent;

  constructor(allMenus: MenuComponent) {
    this.allMenus = allMenus;
  }

  printMenu() {
    this.allMenus.print();
  }
}

要点:

  • 迭代器允许访问聚合的元素,而不暴露其内部结构。
  • 当使用迭代器时,在支持遍历数据的操作方面,我们减轻了聚合的责任。
  • 迭代器提供了一个遍历聚合项的共同接口,当我们编码使用聚合的项时,就可以使用多态。
  • 组合模式允许客户统一地处理组合和个别对象。
  • 组合结构内的任意对象称为组件。组件可以是其他组合或者叶子。

10 状态模式

状态模式允许对象在内部状态改变时改变其行为。对象看起来好像改变了它的类。

2022_07_25_IMG_6555
// 状态接口,每个状态实现这个接口
interface State {
  insertQuarter(): void;
  ejectQuarter(): void;
  turnCrank(): void;
  dispense(): void;
}

class NoQuarterState implements State {
  gumballMachine: GumballMachine;
  constructor(gumballMachine: GumballMachine) {
    this.gumballMachine = gumballMachine;
  }
  insertQuarter(): void {
    console.log('You inserted a quarter');
    this.gumballMachine.setState(this.gumballMachine.getHasQuarterState());
  }
  ejectQuarter(): void {
    console.log("You haven't inserted a quarter");
  }
  turnCrank(): void {
    console.log('You turned,but there is no quarter');
  }
  dispense(): void {
    console.log('You need to pay first');
  }
}

class HasQuarterState implements State {
  gumballMachine: GumballMachine;

  constructor(gumballMachine: GumballMachine) {
    this.gumballMachine = gumballMachine;
  }

  insertQuarter() {
    console.log("You can't insert another quarter");
  }

  ejectQuarter() {
    console.log('Quarter returned');
    this.gumballMachine.setState(this.gumballMachine.getNoQuarterState());
  }

  turnCrank() {
    console.log('You turned...');
    this.gumballMachine.setState(this.gumballMachine.getSoldState());
  }

  dispense() {
    console.log('No gumball dispensed');
  }

  refill() {}

  toString() {
    return 'waiting for turn of crank';
  }
}
class GumballMachine {
  soldOutState: State;
  noQuarterState: State;
  hasQuarterState: State;
  soldState: State;
  winnerState: State;

  state: State;
  count = 0;

  constructor(numberGumballs: number) {
    this.soldState = new SoldState(this);
    this.noQuarterState = new NoQuarterState(this);
    this.hasQuarterState = new HasQuarterState(this);
    this.soldOutState = new SoldOutState(this);
    this.winnerState = new SoldOutState(this);

    this.count = numberGumballs;
    if (numberGumballs > 0) {
      this.state = this.noQuarterState;
    } else {
      this.state = this.soldOutState;
    }
  }
  insertQuarter(): void {
    this.state.insertQuarter();
  }
  ejectQuarter(): void {
    this.state.ejectQuarter();
  }
  turnCrank(): void {
    this.state.turnCrank();
    this.state.dispense();
  }
  setState(state: State) {
    this.state = state;
  }
  releaseBall() {
    console.log('a gumball comes rolling out the slot...');
    if (this.count > 0) {
      this.count--;
    }
  }
  getCount() {
    return this.count;
  }
  getSoldState() {
    return this.soldState;
  }
  getSoldOutState() {
    return this.soldOutState;
  }
  getHasQuarterState() {
    return this.hasQuarterState;
  }
  getNoQuarterState() {
    return this.noQuarterState;
  }
  getWinnerState() {
    return this.winnerState;
  }
}

const gumballMachine = new GumballMachine(5);

console.log(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

要点:

  • 状态模式允许一个对象基于内部状态拥有许多不同的行为。
  • Context把行为委托给所组合的当前状态对象。
  • 通过把每个状态封装进一个类,我们把以后需要做的任何改变局部化。
  • 状态迁移可以由State类或Context类控制。
  • 使用状态模式通常会导致设计中类数目增加。
  • 状态类可以在多个Context实例之间共享。

11 代理模式

2022_07_26_IMG_65592022_07_26_IMG_6560

代理模式为另一个对象提供一个替身或占位符来控制对这个对象的访问。

使用代理模式创建代表对象。代表对象控制对另一个对象的访问,被代表的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

2022_07_26_IMG_6561

要点:

  • 代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理访问的方式有许多种。
  • 远程代理管理客户和远程对象之间的交互。
  • 代理模式有许多其他变体:缓存代理、同步代理、防火墙代理、写入时复制代理等。
  • 代理在结构上类似装饰器,但是目的不同。
  • 装饰者模式为对象加上行为,而代理则是控制访问。

12 复合模式

复合模式

模式通常被一起使用,并结合在同一个设计解决方案中。

复合模式在一个解决方案中结合两个或多个模式,以解决一般的或重复发生的问题。

MVC

复合模式之王——模型-视图-控制器,简称MVC

2022_07_27_IMG_6605

模型使用观察者来保持视图和控制器可以随最新的状态变化而更新。视图和控制器实现了策略模式,控制器是视图的策略,如果想要更换不同的行为,换一个控制器很容易。视图本身内部使用一个模式来管理窗口、按钮,以及其他显示组件:组合模式。

2022_07_28_IMG_6618

要点:

  • 模型-视图-控制器(MVC)是复合模式,结合了观察者、策略和组合模式。
  • 模型使用观察者模式,以便可以保持观察者更新,同时依然保持两者之间的解耦。
  • 控制器是视图的策略,视图可以使用不同的控制器实现,得到不同的行为。
  • 视图使用组合模式实现用户界面。用户界面通常组合了嵌套的组件,像面板、框架和按钮。
  • 适配器模式用于把新的模型适配到已有的视图和控制器。
  • 把MVC模式适配到各种客户端/服务端的应用结构,就得到许多Web MVC框架。

13 更好地与设计模式相处

定义设计模式

模式是在某个上下文中针对某个问题的解决方案。

将设计模式分组分类,分为:创建型、行为型和结构型

  • 创建型模式牵涉到对象实例化,这类模式都提供一种将客户从需要实例化的对象中解耦的方式。
  • 行为型模式都牵涉到类和对象如何交互,以及分配责任。
  • 结构型模式让你组合类或对象得到更大的结构。
创建型模式行为型模式结构型模式
Singleton、Abstract Factory、Factory Method、PrototypeTemplate Method、Command、Iterator、Observer、State、Strategy(策略)Decorator、Composite、Proxy、Facade、Adapter(适配器)

第二种常用的分类方式是:模式所处理的类还是对象

  • 类模式描述类之间的关系如何通过继承定义。类模式中的关系是在编译时建立的。
  • 对象模式描述对象之间的关系,而且对象模式主要通过组合定义。对象模式中的关系通常在运行时创建,更加动态和有弹性。
类模式对象模式
Template Method、Factory Method、AdapterComposite、Decorator、Proxy、Strategy、Abstract Factory、Singleton、Prototype、Facade、Command、Iterator、Observer

快速指南,帮助你开始“用模式思考”:

  • 保持简单

    当你设计时,尽可能用简单的方式解决问题。你的目标应该是简单,而不是“我如何在这个问题中应用模式”。

  • 设计模式不是万灵丹

    模式是解决重复发生的问题的通用解决方案。要使用模式,你还需要全面考虑模式对你的设计中其他部分所造成的后果。

  • 你知道什么时候需要模式

    一旦你确定一个简单的解决方案无法满足你的需要,你应该考虑这个问题以及相关的一组约束,这会帮你将问题匹配到模式。

    有一种情况是,即使更简单的解决方案可行,但当你预测系统某方面会变化时,使用模式。

    并非只有在设计时才考虑引进模式,重构时也要这样做。

  • 重构时间就是模式时间

    重构就是改变你的代码和改进它的组织方式的过程。目标是改善其结构,而不是其行为。

  • 拿掉不是真正需要的。不要害怕从你的设计中移除设计模式

    当你的系统变得非常复杂,而且计划中不需要任何弹性的时候。

  • 如果你现在不需要,就别做

    不要提前开发。

警告:过渡使用设计模式可能导致代码过渡工程化。应该总是用最简单的解决方案完成工作,在需要的地方才引入模式。

  • 模式是工具,不是规则。
  • 为实际的扩展使用模式,不要提供假想的通用性,按需扩展。
  • 简单才是目的。

共享词汇的五种方式

  1. 在设计会议中
  2. 和其他开发人员
  3. 在架构文档中
  4. 在代码注释以及命名习惯上
  5. 将志同道合的开发人员集合在一起

看完这本书,旅途才刚刚开始。

反模式告诉你如何从问题到达一个坏的解决方案。

通过将反模式归档,我们帮助其他人在实现坏解决方案之前,就识别出它们。

要点

  • 让设计模式出现在你的设计中,而不是为了使用而使用。
  • 设计模式并非僵化的教条;可以采纳或调整以符合你的需要。
  • 总是使用满足需要的最简单的解决方案,即使方案里没有包含模式。

14 剩下的模式

桥接(Bridge)

2022_08_01_IMG_6643

生成器(Builder)

2022_08_01_IMG_6644

生成器的优点

  • 封装复杂对象的构造方式。
  • 允许对象用多个步骤构造,并且可以改变过程(和一步到位的工厂不同)。
  • 向客户隐藏产品的内部表现。
  • 产品的实现可以被替换,因为客户只看到抽象的接口

生成器的用途和缺点

  • 经常被用来建造组合结构。
  • 和使用工厂相比,构造对象需要更多客户的领域知识。

责任链(Chain Of Responsibility)

当你要把一个处理请求的机会给予多于一个的独享时,使用责任链模式。

2022_08_01_IMG_6645

责任链的优点

  • 解耦请求的发送者和接收者。
  • 简化你的对象,因为它不需要知道链的结构以及保持对其成员的直接引用。
  • 允许你通过改变链的成员或次序,动态地添加或移除责任。

责任链的用途和缺点

  • 经常用在窗体系统中,处理鼠标点击和键盘事件。
  • 并不保证请求一定会被执行;如果没有对象处理它,可能会掉到链的末尾。
  • 运行时可能会难以观察和调试。

蝇量(Flyweight)

当某个类的一个实例可以用于提供许多虚拟实例时,使用蝇量模式。

2022_08_01_IMG_6646

蝇量的优点

  • 减少运行时对象实例的数目,节省内存。
  • 把许多“虚拟”对象的状态集中放进一个地方。

蝇量的用途和缺点

  • 当一个类有许多实例时,而这些实例能够用一致的方法控制时,用蝇量。
  • 缺点是一旦你实现了它,那么类的单个逻辑实例,将无法拥有和其他实例不同的独立行为。

解释器(Interpreter)

使用解释器模式为语言建造解释器。

解释器的优点

  • 将每一个语法规则表达成一个类,使得语言容易实现。
  • 因为语法由类表达,你可以轻易地改变或扩展该语言。
  • 通过在类结构中添加方法,可以添加解释之外的新行为。例如打印格式的美化或更复杂的程序验证。

解释器的用途和缺点

  • 当你需要实现一门简单的语言时,使用解释器。
  • 当你有一个简单的语法,而且简单比效率重要时,适合用解释器。
  • 用于脚本或编程语言。
  • 当语法规则的数目很大时,这个模式可能变得笨重。在这些情况下,一个解析器/编译器的生成器可能更适合。

中介者(Mediator)

使用中介者模式来集合相关对象之间复杂的沟通和控制方式。

2022_08_01_IMG_6647

中介者的优点

  • 通过将中介者支持的对象从系统解耦,增加对象的复用性。
  • 通过将控制逻辑集中,简化了系统的维护。
  • 简化以及减少系统中对象之间发送的消息的变化。

中介者的用途和缺点

  • 中介者常用于协调相关的GUI组件。
  • 缺点是如果设计不当,中介者对象本身会变得过度复杂。

备忘录(Memento)

当你需要让对象返回之前的某个状态时,例如,你的用户请求“撤销”,使用备忘录模式。

2022_08_01_IMG_6648

备忘录的优点

  • 保持被保存的状态处于关键对象外面,有助于维护内聚。
  • 保持关键对象的数据封装。
  • 提供容易实现的恢复能力。

备忘录的用途和缺点

  • 备忘录用于保存状态。
  • 缺点是保存和恢复状态可能相当耗时。

原型(Prototype)

当创建给定类的实例昂贵或复杂时,使用原型模式。

原型的优点

  • 向客户隐藏制作新实例的复杂性。
  • 提供一种选择,让客户生成类型未知的对象。
  • 在某些环境下,复制对象比创建对象更有效。

原型的用途和缺点

  • 在一个复杂的类层次中,当系统必须创建许多类型的新对象时,应该考虑原型。
  • 使用原型的缺点是,对象的复制有时候相当复杂。

访问者(Visitor)

当你想要为一个对象组合添加能力,且封装不重要时,使用访问者模式。

2022_08_01_IMG_6653

访问者的优点

  • 允许你添加操作到组合结构,而不改变结构本身。
  • 添加新操作相对容易。
  • 由访问者执行的操作的代码被集中化了。

访问者的用途和缺点

  • 使用访问者模式时,组合类的封装被打破。
  • 因为涉及到导游的功能,因此组合结构更加难以变化。
上次更新:
贡献者: chenzilin