31 min read

23种设计模式全解析:优雅地编写高质量的代码(typescript)

大家好!欢迎来到“星辰编程理财”,今天为大家详细介绍23种设计模式,并通过实际示例代码演示它们的用法和优缺点。

设计模式介绍

在前端开发中,设计模式是一种用于解决常见问题的可重用的解决方案。它们是由经验丰富的开发人员和专家所总结和推荐的最佳实践。设计模式不仅可以提高代码的可读性和可维护性,还可以加快开发速度并降低错误的产生。

设计模式的历史和发展

设计模式最早由计算机科学家克里斯托弗·亚历山大(Christopher Alexander)在他的书《建筑的永恒之道》(A Pattern Language)中引入。后来,设计模式逐渐被应用到软件开发领域,以提高软件系统的可扩展性和可重用性。

设计模式的原则和注意事项

SOLID 原则

SOLID 是面向对象设计中常用的五个原则的缩写:

  • 单一职责原则 (Single Responsibility Principle, SRP): 一个类应该只有一个引起变化的原因。这意味着一个类应该只负责一件事情。
  • 开放封闭原则 (Open-Closed Principle, OCP): 软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,不应该修改现有代码,而是应该通过扩展已有代码来实现。
  • 里氏替换原则 (Liskov Substitution Principle, LSP): 子类必须能够替换其父类。这意味着在使用基类的地方,应该能够安全地使用其任何子类。
  • 接口隔离原则 (Interface Segregation Principle, ISP): 客户端不应该强迫依赖于它们不使用的接口。这意味着一个类不应该依赖于它不需要的接口,而是应该定义自己需要的接口。
  • 依赖倒置原则 (Dependency Inversion Principle, DIP): 高级模块不应该依赖于低级模块,而是应该依赖于抽象。这意味着应该通过接口来定义依赖关系,而不是具体的实现类。

设计模式的适用性和局限性

设计模式并不适用于所有情况,它们只是一种解决问题的工具。在选择使用设计模式时,我们需要考虑以下几个因素:

  • 问题的复杂性: 如果问题较为简单,使用设计模式可能会增加代码的复杂性和开发时间。因此,在解决简单问题时,我们可以考虑不使用设计模式。
  • 项目规模和团队规模: 在大型项目和大型团队中,使用设计模式可以提高代码的可读性和可维护性,并减少错误的产生。
  • 未来的扩展性: 如果我们预计系统需要频繁地进行功能扩展和修改,使用设计模式可以提供更好的灵活性和可扩展性。

设计模式的发展趋势和最佳实践

设计模式的发展是一个不断演化的过程。随着技术的进步和软件开发领域的发展,新的设计模式不断涌现。同时,我们也需要根据具体的项目需求和团队情况来选择合适的设计模式。以下是一些设计模式的最佳实践:

  • 熟悉常见的设计模式: 熟悉并理解常见的设计模式,以便在开发过程中能够应用它们。
  • 根据情况选择合适的设计模式: 在选择设计模式时,要根据具体的项目需求和团队情况来判断其适用性。
  • 使用设计模式解决具体问题: 在解决实际问题时,要结合具体情况选择适当的设计模式。

创建型模式

创建型模式主要关注如何创建对象,以及对象的实例化过程。它们提供了一种创建对象的方式,使得系统可以独立于对象的具体类进行实例化。

工厂模式

工厂模式是一种常见的创建型模式,它通过定义一个用于创建对象的接口,让子类决定实例化哪个类。优点是封装了对象的创建过程,使客户端代码与具体类解耦。缺点是当需要添加新的产品时,需要修改工厂类的代码。

使用场景:
工厂模式适用于需要根据不同的条件创建不同类型对象的情况。比如,我们可以根据用户的不同等级,创建不同权限的用户对象。

代码示例:

interface Product {
  use(): void;
}

class ConcreteProduct implements Product {
  use() {
    console.log('Using ConcreteProduct');
  }
}

class Factory {
  createProduct(): Product {
    return new ConcreteProduct();
  }
}

const factory = new Factory();
const product = factory.createProduct();
product.use();

抽象工厂模式

抽象工厂模式是一种用于创建一系列相关或相互依赖的对象的创建型模式。它提供了一个接口,用于创建一族产品,而不需要指定具体的产品类。优点是可以在不修改现有代码的情况下引入新的产品系列,缺点是难以支持新种类的产品。

使用场景:
抽象工厂模式适用于需要创建多个相关对象,并且这些对象之间有一定约束关系的情况。比如,我们可以使用抽象工厂模式创建一个UI组件库,其中包含了多种样式和风格的按钮、输入框等组件。

代码示例:

interface AbstractProductA {
  use(): void;
}

interface AbstractProductB {
  use(): void;
}

class ConcreteProductA1 implements AbstractProductA {
  use() {
    console.log('Using ConcreteProductA1');
  }
}

class ConcreteProductB1 implements AbstractProductB {
  use() {
    console.log('Using ConcreteProductB1');
  }
}

class ConcreteProductA2 implements AbstractProductA {
  use() {
    console.log('Using ConcreteProductA2');
  }
}

class ConcreteProductB2 implements AbstractProductB {
  use() {
    console.log('Using ConcreteProductB2');
  }
}

interface AbstractFactory {
  createProductA(): AbstractProductA;
  createProductB(): AbstractProductB;
}

class ConcreteFactory1 implements AbstractFactory {
  createProductA(): AbstractProductA {
    return new ConcreteProductA1();
  }

  createProductB(): AbstractProductB {
    return new ConcreteProductB1();
  }
}

class ConcreteFactory2 implements AbstractFactory {
  createProductA(): AbstractProductA {
    return new ConcreteProductA2();
  }

  createProductB(): AbstractProductB {
    return new ConcreteProductB2();
  }
}

const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
productA1.use();
productB1.use();

const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
productA2.use();
productB2.use();

单例模式

单例模式是一种创建型模式,它保证一个类只有一个实例,并提供了一个全局访问点。优点是可以节省内存,避免资源的重复创建,缺点是对于多线程环境下可能会引发竞态条件。

使用场景:
单例模式适用于需要共享一些公共资源的情况。比如,我们可以使用单例模式来创建一个全局的日志对象,在整个应用程序中都可以使用这个对象来记录日志信息。

代码示例:

class Singleton {
  private static instance: Singleton;

  private constructor() {}

  static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  use() {
    console.log('Using Singleton');
  }
}

const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();

console.log(singleton1 === singleton2); // true

singleton1.use();

原型模式

原型模式是一种通过复制现有对象来创建新对象的创建型模式。它通过克隆原型来创建新的对象,而不需要通过实例化类。优点是可以避免重复初始化对象,缺点是需要在每个可克隆的类中实现克隆方法。

使用场景:
原型模式适用于需要创建多个相似对象的情况。比如,我们可以使用原型模式来创建一个表单验证器的原型对象,然后根据需要进行复制,从而快速创建多个表单验证器。

代码示例:

abstract class Prototype {
  abstract clone(): Prototype;
}

class ConcretePrototype1 extends Prototype {
  clone(): Prototype {
    return new ConcretePrototype1();
  }
}

class ConcretePrototype2 extends Prototype {
  clone(): Prototype {
    return new ConcretePrototype2();
  }
}

const prototype1 = new ConcretePrototype1();
const clone1 = prototype1.clone();

console.log(prototype1 !== clone1); // true

建造者模式

建造者模式是一种创建型模式,它将对象的构建过程和表示分离,使同样的构建过程可以创建不同的表示。优点是可以通过相同的构建过程构建不同的产品,缺点是需要为每种表示编写具体的建造者类。

使用场景:
建造者模式适用于需要构建复杂对象的情况。比如,我们可以使用建造者模式来构建一个包含多个步骤的表单,每个步骤可以由不同的构建者来完成。

代码示例:

class Product {
  private parts: string[] = [];

  addPart(part: string) {
    this.parts.push(part);
  }

  listParts() {
    console.log(`Parts: ${this.parts.join(', ')}`);
  }
}

interface Builder {
  buildPartA(): void;
  buildPartB(): void;
  getProduct(): Product;
}

class ConcreteBuilder1 implements Builder {
  private product: Product;

  constructor() {
    this.reset();
  }

  reset() {
    this.product = new Product();
  }

  buildPartA() {
    this.product.addPart('Part A1');
  }

  buildPartB() {
    this.product.addPart('Part B1');
  }

  getProduct(): Product {
    const result = this.product;
    this.reset();
    return result;
  }
}

class ConcreteBuilder2 implements Builder {
  private product: Product;

  constructor() {
    this.reset();
  }

  reset() {
    this.product = new Product();
  }

  buildPartA() {
    this.product.addPart('Part A2');
  }

  buildPartB() {
    this.product.addPart('Part B2');
  }

  getProduct(): Product {
    const result = this.product;
    this.reset();
    return result;
  }
}

class Director {
  private builder: Builder;

  setBuilder(builder: Builder) {
    this.builder = builder;
  }

  buildMinimalProduct() {
    this.builder.buildPartA();
  }

  buildFullProduct() {
    this.builder.buildPartA();
    this.builder.buildPartB();
  }
}

const director = new Director();

const builder1 = new ConcreteBuilder1();
director.setBuilder(builder1);
director.buildFullProduct();
const product1 = builder1.getProduct();
product1.listParts();

const builder2 = new ConcreteBuilder2();
director.setBuilder(builder2);
director.buildMinimalProduct();
const product2 = builder2.getProduct();
product2.listParts();

结构型模式

结构型模式主要关注如何组合类和对象以形成更大的结构,并提供新的功能。它们通过组合不同的类和对象来解决特定的设计问题,以实现更灵活、可扩展和可维护的系统结构。

适配器模式

适配器模式是一种结构型模式,它通过将一个类的接口转换成客户端所期望的接口来适应不兼容的接口。优点是可以使不兼容的类一起工作,缺点是增加了代码的复杂性。

使用场景:
当希望使用一个已经存在的类,但是它的接口不符合需求时,可以使用适配器模式将其接口转换成合适的接口。

代码示例:

interface Target {
  request(): void;
}

class Adaptee {
  specificRequest() {
    console.log('Specific request');
  }
}

class Adapter implements Target {
  private adaptee: Adaptee;

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

  request() {
    this.adaptee.specificRequest();
  }
}

const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
adapter.request();

桥接模式

桥接模式是一种结构型模式,它将抽象部分和实现部分分离,使它们可以独立地变化。优点是可以减少类的数量,提高系统的可扩展性,缺点是增加了系统的复杂性。

使用场景:
桥接模式适用于需要在抽象部分和实现部分之间存在多对多关系的情况。比如,我们可以使用桥接模式将多个颜色和形状进行组合,从而得到不同颜色和形状的组合结果。

interface Implementor {
  operationImpl(): void;
}

class ConcreteImplementorA implements Implementor {
  operationImpl() {
    console.log('ConcreteImplementorA operation');
  }
}

class ConcreteImplementorB implements Implementor {
  operationImpl() {
    console.log('ConcreteImplementorB operation');
  }
}

abstract class Abstraction {
  protected implementor: Implementor;

  constructor(implementor: Implementor) {
    this.implementor = implementor;
  }

  abstract operation(): void;
}

class RefinedAbstraction extends Abstraction {
  operation() {
    this.implementor.operationImpl();
  }
}

const implementorA = new ConcreteImplementorA();
const abstractionA = new RefinedAbstraction(implementorA);
abstractionA.operation();

const implementorB = new ConcreteImplementorB();
const abstractionB = new RefinedAbstraction(implementorB);
abstractionB.operation();

装饰器模式

装饰器模式是一种结构型模式,它动态地将责任附加到对象上。它提供了一种灵活的方式来扩展对象的功能。优点是可以在不改变原始对象的情况下添加新的功能,缺点是增加了类的数量。

使用场景:
装饰器模式适用于需要对一个对象进行功能扩展的情况。比如,我们可以使用装饰器模式来给一个文本编辑器添加自动保存、自动备份等功能。

代码示例:

interface Component {
  operation(): void;
}

class ConcreteComponent implements Component {
  operation() {
    console.log('ConcreteComponent operation');
  }
}

class Decorator implements Component {
  protected component: Component;

  constructor(component: Component) {
    this.component = component;
  }

  operation() {
    this.component.operation();
  }
}

class ConcreteDecoratorA extends Decorator {
  operation() {
    super.operation();
    this.additionalOperation();
  }

  additionalOperation() {
    console.log('Additional operation in ConcreteDecoratorA');
  }
}

class ConcreteDecoratorB extends Decorator {
  operation() {
    super.operation();
    this.additionalOperation();
  }

  additionalOperation() {
    console.log('Additional operation in ConcreteDecoratorB');
  }
}

const component = new ConcreteComponent();

const decoratorA = new ConcreteDecoratorA(component);
decoratorA.operation();

const decoratorB = new ConcreteDecoratorB(component);
decoratorB.operation();

const decoratorAB = new ConcreteDecoratorB(decoratorA);
decoratorAB.operation();

组合模式

组合模式是一种结构型模式,它将对象组织成树形结构,以表示部分-整体的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。优点是简化了客户端代码,使得新增类型的处理更加容易,缺点是限制了组合对象的类型。

使用场景:
组合模式适用于需要处理层次结构数据的情况。比如,我们可以使用组合模式来表示一个多级菜单,其中每个菜单项都可以包含子菜单。

以下是组合模式的代码示例:

abstract class Component {
  protected parent: Component | null;

  setParent(parent: Component | null) {
    this.parent = parent;
  }

  getParent(): Component | null {
    return this.parent;
  }

  add(component: Component): void {}

  remove(component: Component): void {}

  isComposite(): boolean {
    return false;
  }

  abstract operation(): void;
}

class Leaf extends Component {
  operation() {
    console.log('Leaf operation');
  }
}

class Composite extends Component {
  protected children: Component[] = [];

  add(component: Component) {
    this.children.push(component);
    component.setParent(this);
  }

  remove(component: Component) {
    const index = this.children.indexOf(component);
    if (index !== -1) {
      this.children.splice(index, 1);
      component.setParent(null);
    }
  }

  isComposite(): boolean {
    return true;
  }

  operation() {
    console.log('Composite operation');
    for (const child of this.children) {
      child.operation();
    }
  }
}

const leaf1 = new Leaf();
leaf1.operation();

const leaf2 = new Leaf();
leaf2.operation();

const composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);
composite.operation();

外观模式

外观模式是一种结构型模式,它为一组复杂的子系统提供了一个简化的接口。通过引入一个外观类,可以减少与子系统的直接交互,从而简化了客户端代码。优点是简化了客户端代码,缺点是增加了子系统的耦合性。

使用场景:
外观模式适用于需要对一个复杂子系统进行封装的情况。比如,我们可以使用外观模式来封装一个包含多个接口调用的功能模块。

代码示例:

class SubsystemA {
  operationA() {
    console.log('SubsystemA operation');
  }
}

class SubsystemB {
  operationB() {
    console.log('SubsystemB operation');
  }
}

class SubsystemC {
  operationC() {
    console.log('SubsystemC operation');
  }
}

class Facade {
  private subsystemA: SubsystemA;
  private subsystemB: SubsystemB;
  private subsystemC: SubsystemC;

  constructor() {
    this.subsystemA = new SubsystemA();
    this.subsystemB = new SubsystemB();
    this.subsystemC = new SubsystemC();
  }

  operation() {
    this.subsystemA.operationA();
    this.subsystemB.operationB();
    this.subsystemC.operationC();
  }
}

const facade = new Facade();
facade.operation();

享元模式

享元模式是一种结构型模式,它通过共享尽可能多的对象来最小化内存使用和对象数量。它通过将对象的外部状态与内部状态分离来实现共享。优点是减少了对象的数量,节省了内存,缺点是增加了代码的复杂性。

使用场景:
享元模式适用于需要创建大量相似对象的情况。比如,我们可以使用享元模式来创建一个图标库,其中包含了多种类型和样式的图标。

代码示例:

class Flyweight {
  private sharedState: string;

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

  operation(uniqueState: string) {
    console.log(`Flyweight with shared state ${this.sharedState} and unique state ${uniqueState}`);
  }
}

class FlyweightFactory {
  private flyweights: { [key: string]: Flyweight } = {};

  getFlyweight(sharedState: string): Flyweight {
    if (!this.flyweights[sharedState]) {
      this.flyweights[sharedState] = new Flyweight(sharedState);
    }
    return this.flyweights[sharedState];
  }
}

const factory = new FlyweightFactory();

const flyweight1 = factory.getFlyweight('shared state 1');
flyweight1.operation('unique state 1');

const flyweight2 = factory.getFlyweight('shared state 2');
flyweight2.operation('unique state 2');

const flyweight3 = factory.getFlyweight('shared state 1');
flyweight3.operation('unique state 3');

console.log(flyweight1 === flyweight3); // true

代理模式

代理模式是一种结构型模式,它提供了一个代理对象来控制对另一个对象的访问。通过使用代理,客户端可以间接地与真实对象进行交互。优点是可以对客户端隐藏真实对象的细节,缺点是增加了代码的复杂性。

使用场景:
代理模式适用于需要对一个对象进行控制或增强的情况。比如,我们可以使用代理模式来实现一个缓存代理,从而提高对某个对象的访问速度。

代码示例:

interface Subject {
  request(): void;
}

class RealSubject implements Subject {
  request() {
    console.log('RealSubject request');
  }
}

class Proxy implements Subject {
  private realSubject: RealSubject;

  constructor(realSubject: RealSubject) {
    this.realSubject = realSubject;
  }

  request() {
    this.preRequest();
    this.realSubject.request();
    this.postRequest();
  }

  preRequest() {
    console.log('Proxy preRequest');
  }

  postRequest() {
    console.log('Proxy postRequest');
  }
}

const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);
proxy.request();

行为型模式

行为型模式主要关注对象之间的通信和协作,以及对象如何分配职责和管理复杂的行为。它们描述了对象之间的交互方式,以及如何将行为分配到不同的对象中。

策略模式

策略模式是一种定义一系列算法的方法,并将其封装起来,使其可以相互替换。策略模式可以使算法的变化独立于使用它的客户端。

优点:

  • 算法可以独立于客户端而变化,符合开闭原则。
  • 可以减少代码中大量的条件语句。

缺点:

  • 客户端需要了解所有的策略类,并自行选择使用哪个策略。

使用场景:

  • 当需要在多个算法中选择一种时,可以使用策略模式。
  • 当有多个条件语句判断时,可以使用策略模式简化代码。

代码示例:

// 策略接口
interface Strategy {
    execute(): void;
}

// 具体策略A
class ConcreteStrategyA implements Strategy {
    execute() {
        console.log('执行策略A');
    }
}

// 具体策略B
class ConcreteStrategyB implements Strategy {
    execute() {
        console.log('执行策略B');
    }
}

// 上下文
class Context {
    private strategy: Strategy;

    constructor(strategy: Strategy) {
        this.strategy = strategy;
    }

    setStrategy(strategy: Strategy) {
        this.strategy = strategy;
    }

    executeStrategy() {
        this.strategy.execute();
    }
}

// 使用策略模式
const context: Context = new Context(new ConcreteStrategyA());
context.executeStrategy();

context.setStrategy(new ConcreteStrategyB());
context.executeStrategy();

观察者模式

观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生变化时,其依赖者都会收到通知并自动更新。

优点:

  • 可以实现对象间的解耦,使得依赖者和被依赖者之间的关系更加灵活。
  • 可以支持广播通信,一个被观察者可以有多个观察者。

缺点:

  • 如果观察者过多或通知过于频繁,会影响系统性能。

使用场景:

  • 当一个对象的改变需要同时改变其他对象时,可以使用观察者模式。
  • 当一个对象需要将自己的改变通知给其他对象时,可以使用观察者模式。

代码示例:

// 被观察者接口
interface Subject {
    registerObserver(observer: Observer): void;
    removeObserver(observer: Observer): void;
    notifyObservers(): void;
}

// 观察者接口
interface Observer {
    update(data: any): void;
}

// 具体被观察者
class ConcreteSubject implements Subject {
    private observers: Observer[] = [];
    private data: any;

    registerObserver(observer: Observer) {
        this.observers.push(observer);
    }

    removeObserver(observer: Observer) {
        const index = this.observers.indexOf(observer);
        if (index !== -1) {
            this.observers.splice(index, 1);
        }
    }

    notifyObservers() {
        for (const observer of this.observers) {
            observer.update(this.data);
        }
    }

    setData(data: any) {
        this.data = data;
        this.notifyObservers();
    }
}

// 具体观察者A
class ConcreteObserverA implements Observer {
    update(data: any) {
        console.log('观察者A收到通知,数据:', data);
    }
}

// 具体观察者B
class ConcreteObserverB implements Observer {
    update(data: any) {
        console.log('观察者B收到通知,数据:', data);
    }
}

// 使用观察者模式
const subject: Subject = new ConcreteSubject();
const observerA: Observer = new ConcreteObserverA();
const observerB: Observer = new ConcreteObserverB();

subject.registerObserver(observerA);
subject.registerObserver(observerB);

subject.setData('hello');

责任链模式

责任链模式是一种将请求的发送和接收解耦的设计模式,多个对象按照顺序形成一条链,请求沿着链传递,直到有一个对象处理它。

优点:

  • 解耦了发送者和接收者,使得代码更加灵活、可维护和可扩展。
  • 可以动态地改变链中的处理顺序。

缺点:

  • 请求可能无法被处理,或者没有到达链尾时会导致请求被丢失。

使用场景:

  • 当需要避免请求的发送者与接收者之间的耦合关系时,可以使用责任链模式。
  • 当希望动态地改变处理顺序时,可以使用责任链模式。

代码示例:

// 请求类
class Request {
    private type: string;

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

    getType() {
        return this.type;
    }
}

// 处理者抽象类
abstract class Handler {
    private nextHandler: Handler;

    setNextHandler(nextHandler: Handler) {
        this.nextHandler = nextHandler;
    }

    handleRequest(request: Request) {
        if (this.canHandleRequest(request)) {
            this.handle(request);
        } else if (this.nextHandler) {
            this.nextHandler.handleRequest(request);
        } else {
            console.log('无处理者可以处理该请求');
        }
    }

    protected abstract canHandleRequest(request: Request): boolean;

    protected abstract handle(request: Request): void;
}

// 具体处理者A
class ConcreteHandlerA extends Handler {
    protected canHandleRequest(request: Request): boolean {
        return request.getType() === 'A';
    }

    protected handle(request: Request): void {
        console.log('处理者A处理请求');
    }
}

// 具体处理者B
class ConcreteHandlerB extends Handler {
    protected canHandleRequest(request: Request): boolean {
        return request.getType() === 'B';
    }

    protected handle(request: Request): void {
        console.log('处理者B处理请求');
    }
}

// 使用责任链模式
const handlerA: Handler = new ConcreteHandlerA();
const handlerB: Handler = new ConcreteHandlerB();

handlerA.setNextHandler(handlerB);

const requestA: Request = new Request('A');
handlerA.handleRequest(requestA);

const requestB: Request = new Request('B');
handlerA.handleRequest(requestB);

命令模式

命令模式是一种将请求封装成对象的设计模式,使得请求的发送者与请求的接收者之间解耦。命令模式可以将请求参数化,将请求操作的对象和被调用者分离。

优点:

  • 解耦了请求的发送者和接收者,使得代码更加灵活、可维护和可扩展。
  • 可以将多个请求组合成一个复合请求。

缺点:

  • 增加了代码的复杂度。

使用场景:

  • 当需要解耦请求的发送者和接收者时,可以使用命令模式。
  • 当需要将多个请求组合成一个复合请求时,可以使用命令模式。

代码示例:

// 命令接口
interface Command {
    execute(): void;
}

// 具体命令A
class ConcreteCommandA implements Command {
    private receiver: Receiver;

    constructor(receiver: Receiver) {
        this.receiver = receiver;
    }

    execute() {
        this.receiver.actionA();
    }
}

// 具体命令B
class ConcreteCommandB implements Command {
    private receiver: Receiver;

    constructor(receiver: Receiver) {
        this.receiver = receiver;
    }

    execute() {
        this.receiver.actionB();
    }
}

// 接收者
class Receiver {
    actionA() {
        console.log('接收者执行操作A');
    }

    actionB() {
        console.log('接收者执行操作B');
    }
}

// 请求者
class Invoker {
    private commands: Command[] = [];

    addCommand(command: Command) {
        this.commands.push(command);
    }

    executeCommands() {
        for (const command of this.commands) {
            command.execute();
        }
    }
}

// 使用命令模式
const receiver: Receiver = new Receiver();

const commandA: Command = new ConcreteCommandA(receiver);
const commandB: Command = new ConcreteCommandB(receiver);

const invoker: Invoker = new Invoker();
invoker.addCommand(commandA);
invoker.addCommand(commandB);

invoker.executeCommands();

迭代器模式

迭代器模式是一种顺序访问集合对象元素的方法,不需要知道集合对象的底层实现。

优点:

  • 将集合对象的遍历与集合对象本身解耦,使得代码更加灵活、可维护和可扩展。
  • 简化了集合对象的接口。

缺点:

  • 增加了代码的复杂度。

使用场景:

  • 当需要访问一个集合对象的元素而无需暴露其内部实现时,可以使用迭代器模式。
  • 当需要对不同类型的集合对象提供统一的遍历接口时,可以使用迭代器模式。

代码示例:

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

// 具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
    private collection: T[];
    private index: number = 0;

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

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

    next(): T {
        return this.collection[this.index++];
    }
}

// 集合类接口
interface Aggregate<T> {
    createIterator(): Iterator<T>;
}

// 具体集合类
class ConcreteAggregate<T> implements Aggregate<T> {
    private collection: T[];

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

    createIterator(): Iterator<T> {
        return new ConcreteIterator(this.collection);
    }
}

// 使用迭代器模式
const collection: string[] = ['a', 'b', 'c'];
const aggregate: Aggregate<string> = new ConcreteAggregate(collection);
const iterator: Iterator<string> = aggregate.createIterator();

while (iterator.hasNext()) {
    console.log(iterator.next());
}

中介者模式

中介者模式定义了一个中介对象,该对象封装了一系列对象之间的交互方式。通过使用中介者,对象之间的通信将通过中介者进行,而不是直接彼此交互。

优点:

  • 将对象之间的通信解耦,使得代码更加灵活、可维护和可扩展。
  • 可以减少对象之间的直接关联,降低系统的复杂性。

缺点:

  • 中介者对象可能变得非常复杂,难以维护。

使用场景:

  • 当需要将一组对象之间的通信解耦时,可以使用中介者模式。
  • 当对象之间的交互逻辑较复杂时,可以使用中介者模式。

代码示例:

// 中介者接口
interface Mediator {
    send(message: string, colleague: Colleague): void;
}

// 同事类抽象类
abstract class Colleague {
    protected mediator: Mediator;

    constructor(mediator: Mediator) {
        this.mediator = mediator;
    }

    abstract receive(message: string): void;
    abstract send(message: string): void;
}

// 具体中介者
class ConcreteMediator implements Mediator {
    private colleagueA: Colleague;
    private colleagueB: Colleague;

    setColleagueA(colleague: Colleague) {
        this.colleagueA = colleague;
    }

    setColleagueB(colleague: Colleague) {
        this.colleagueB = colleague;
    }

    send(message: string, colleague: Colleague) {
        if (colleague === this.colleagueA) {
            this.colleagueB.receive(message);
        } else if (colleague === this.colleagueB) {
            this.colleagueA.receive(message);
        }
    }
}

// 具体同事A
class ConcreteColleagueA extends Colleague {
    receive(message: string) {
        console.log('同事A收到消息:', message);
    }

    send(message: string) {
        this.mediator.send(message, this);
    }
}

// 具体同事B
class ConcreteColleagueB extends Colleague {
    receive(message: string) {
        console.log('同事B收到消息:', message);
    }

    send(message: string) {
        this.mediator.send(message, this);
    }
}

// 使用中介者模式
const mediator: Mediator = new ConcreteMediator();
const colleagueA: Colleague = new ConcreteColleagueA(mediator);
const colleagueB: Colleague = new ConcreteColleagueB(mediator);

mediator.setColleagueA(colleagueA);
mediator.setColleagueB(colleagueB);

colleagueA.send('hello');
colleagueB.send('world');

备忘录模式

备忘录模式是一种保存对象状态的设计模式,它允许在不破坏封装性的前提下,捕获并保存对象内部的状态,并且可以在以后恢复到该状态。

优点:

  • 保存对象的状态,使得可以在需要的时候恢复到先前的状态。
  • 对象的状态可以被完全封装起来,不会被外部访问。

缺点:

  • 如果需要保存的状态较多,会占用大量的内存。

使用场景:

  • 当需要保存和恢复对象的状态时,可以使用备忘录模式。
  • 当需要实现撤销和恢复功能时,可以使用备忘录模式。

代码示例:

// 备忘录
class Memento {
    private state: string;

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

    getState() {
        return this.state;
    }
}

// 原发器
class Originator {
    private state: string;

    setState(state: string) {
        this.state = state;
    }

    getState() {
        return this.state;
    }

    createMemento(): Memento {
        return new Memento(this.state);
    }

    restoreMemento(memento: Memento) {
        this.state = memento.getState();
    }
}

// 管理者
class Caretaker {
    private mementos: Memento[] = [];

    addMemento(memento: Memento) {
        this.mementos.push(memento);
    }

    getMemento(index: number) {
        return this.mementos[index];
    }
}

// 使用备忘录模式
const originator: Originator = new Originator();
const caretaker: Caretaker = new Caretaker();

originator.setState('State 1');
console.log('当前状态:', originator.getState());

const memento: Memento = originator.createMemento();
caretaker.addMemento(memento);

originator.setState('State 2');
console.log('当前状态:', originator.getState());

originator.restoreMemento(caretaker.getMemento(0));
console.log('恢复后的状态:', originator.getState());

解释器模式

解释器模式是一种用于解决特定类型问题的行为模式,它定义了一种语言语法的表示,并解释该语法。

优点:

  • 可以定义语法规则的表达式。
  • 易于改变和扩展语法。

缺点:

  • 可能会导致类的数量急剧增加。

使用场景:

  • 当需要解释一种语言的语法时,可以使用解释器模式。
  • 当需要定义一种语法的表达式并解释该语法时,可以使用解释器模式。

代码示例:

// 上下文
class Context {
    private input: string;
    private output: number;

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

    getInput() {
        return this.input;
    }

    setInput(input: string) {
        this.input = input;
    }

    getOutput() {
        return this.output;
    }

    setOutput(output: number) {
        this.output = output;
    }
}

// 抽象表达式
abstract class Expression {
    abstract interpret(context: Context): void;
}

// 终结符表达式
class TerminalExpression extends Expression {
    interpret(context: Context) {
        context.setOutput(parseInt(context.getInput()));
    }
}

// 非终结符表达式
class NonterminalExpression extends Expression {
    private expression1: Expression;
    private expression2: Expression;

    constructor(expression1: Expression, expression2: Expression) {
        super();
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    interpret(context: Context) {
        const input = context.getInput();
        const index = input.indexOf(' ');

        if (index !== -1) {
            const subInput1 = input.substring(0, index);
            const subInput2 = input.substring(index + 1);
            context.setInput(subInput2);
            this.expression1.interpret(context);
            this.expression2.interpret(context);
        }
    }
}

// 使用解释器模式
const expression1: Expression = new TerminalExpression();
const expression2: Expression = new NonterminalExpression(expression1, expression1);

const context: Context = new Context('100 200');
expression2.interpret(context);

console.log('解释结果:', context.getOutput());

状态模式

状态模式是一种将对象的行为封装成不同状态的设计模式,使得对象在不同状态下可以改变其行为。

优点:

  • 将状态与行为分离,使代码更加清晰、灵活和易于维护。
  • 符合开闭原则。

缺点:

  • 增加了系统的复杂性。

使用场景:

  • 当一个对象的行为取决于其状态,并且需要根据状态动态地改变行为时,可以使用状态模式。
  • 当一个对象有多个状态且状态之间会发生转换时,可以使用状态模式。

代码示例:

// 状态接口
interface State {
    handle(context: Context): void;
}

// 具体状态A
class ConcreteStateA implements State {
    handle(context: Context) {
        console.log('当前状态为A,将转换到下一个状态B');
        context.setState(new ConcreteStateB());
    }
}

// 具体状态B
class ConcreteStateB implements State {
    handle(context: Context) {
        console.log('当前状态为B,将转换到下一个状态A');
        context.setState(new ConcreteStateA());
    }
}

// 上下文
class Context {
    private state: State;

    constructor() {
        this.state = new ConcreteStateA();
    }

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

    request() {
        this.state.handle(this);
    }
}

// 使用状态模式
const context: Context = new Context();

context.request();
context.request();
context.request();

模板方法模式

模板方法模式是一种基于继承的设计模式,它定义了一个算法的骨架,而将一些步骤的实现延迟到子类中。模板方法模式提供了一种封装算法的方式,使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现。

优点:

  • 封装不变的部分,提取可变的部分,使得代码更易于复用和扩展。
  • 提供了一种良好的代码复用机制,遵循开闭原则。

缺点:

  • 如果子类中的某些步骤需要变更,那么就需要修改父类的算法骨架。
  • 增加了类的个数,增加了系统的复杂性。

使用场景:

  • 多个子类有共同的行为,而又需要在其中的某些步骤中有所区别。
  • 重要的、复杂的算法可以使用模板方法模式提供一个框架,将具体实现延迟到子类中。

代码示例:

// 抽象类
abstract class AbstractClass {
  // 模板方法
  public templateMethod(): void {
    this.baseOperation1();
    this.requiredOperation1();
    this.baseOperation2();
    this.hook();
  }

  // 基本操作1
  protected baseOperation1(): void {
    console.log('抽象类中的基本操作1');
  }

  // 基本操作2
  protected baseOperation2(): void {
    console.log('抽象类中的基本操作2');
  }

  // 钩子操作
  protected hook(): void {}
  
  // 必需的操作1,延迟到子类中实现
  protected abstract requiredOperation1(): void;
}

// 具体类A
class ConcreteClassA extends AbstractClass {
  protected requiredOperation1(): void {
    console.log('具体类A中的必需操作1');
  }
}

// 具体类B
class ConcreteClassB extends AbstractClass {
  protected requiredOperation1(): void {
    console.log('具体类B中的必需操作1');
  }

  // 重写钩子操作
  protected hook(): void {
    console.log('具体类B中的钩子操作');
  }
}

// 客户端
const concreteClassA = new ConcreteClassA();
concreteClassA.templateMethod();
// 输出:
// 抽象类中的基本操作1
// 具体类A中的必需操作1
// 抽象类中的基本操作2

const concreteClassB = new ConcreteClassB();
concreteClassB.templateMethod();
// 输出:
// 抽象类中的基本操作1
// 具体类B中的必需操作1
// 抽象类中的基本操作2
// 具体类B中的钩子操作

访问者模式

访问者模式是一种将算法与对象结构分离的设计模式,它可以在不改变对象结构的前提下,增加新的操作。访问者模式通过定义一个访问者接口和多个具体访问者,来实现对不同对象结构的访问。

优点:

  • 增加新的操作很方便,符合开闭原则。
  • 将数据结构和数据操作解耦,增加了系统的灵活性和可维护性。

缺点:

  • 增加了新的元素类时,需要修改访问者接口和所有具体访问者的实现类。
  • 在访问者模式中,元素类之间的关系并没有得到维护,违反了迪米特法则。

使用场景:

  • 当一个对象结构中的对象需要进行不同的操作,并且操作的种类经常变化时,可以使用访问者模式。
  • 当一个对象结构中的对象有很多不同的类,并且需要对这些类进行一些公共操作时,可以使用访问者模式。

代码示例:

// 抽象访问者
interface Visitor {
  visitConcreteElementA(element: ConcreteElementA): void;
  visitConcreteElementB(element: ConcreteElementB): void;
}

// 具体访问者1
class ConcreteVisitor1 implements Visitor {
  public visitConcreteElementA(element: ConcreteElementA): void {
    console.log('具体访问者1访问具体元素A');
    element.operationA();
  }

  public visitConcreteElementB(element: ConcreteElementB): void {
    console.log('具体访问者1访问具体元素B');
    element.operationB();
  }
}

// 具体访问者2
class ConcreteVisitor2 implements Visitor {
  public visitConcreteElementA(element: ConcreteElementA): void {
    console.log('具体访问者2访问具体元素A');
    element.operationA();
  }

  public visitConcreteElementB(element: ConcreteElementB): void {
    console.log('具体访问者2访问具体元素B');
    element.operationB();
  }
}

// 抽象元素
interface Element {
  accept(visitor: Visitor): void;
}

// 具体元素A
class ConcreteElementA implements Element {
  public accept(visitor: Visitor): void {
    visitor.visitConcreteElementA(this);
  }

  public operationA(): void {
    console.log('具体元素A的操作');
  }
}

// 具体元素B
class ConcreteElementB implements Element {
  public accept(visitor: Visitor): void {
    visitor.visitConcreteElementB(this);
  }

  public operationB(): void {
    console.log('具体元素B的操作');
  }
}

// 对象结构
class ObjectStructure {
  private elements: Element[] = [];

  public attach(element: Element): void {
    this.elements.push(element);
  }

  public detach(element: Element): void {
    const index = this.elements.indexOf(element);
    if (index !== -1) {
      this.elements.splice(index, 1);
    }
  }

  public accept(visitor: Visitor): void {
    for (const element of this.elements) {
      element.accept(visitor);
    }
  }
}

// 客户端
const objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());

const visitor1 = new ConcreteVisitor1();
objectStructure.accept(visitor1);
// 输出:
// 具体访问者1访问具体元素A
// 具体元素A的操作
// 具体访问者1访问具体元素B
// 具体元素B的操作

const visitor2 = new ConcreteVisitor2();
objectStructure.accept(visitor2);
// 输出:
// 具体访问者2访问具体元素A
// 具体元素A的操作
// 具体访问者2访问具体元素B
// 具体元素B的操作

实战应用

使用设计模式解决实际问题的案例分析

以下是一个使用观察者模式解决实际问题的案例:

// 定义一个主题接口
interface Subject {
  attach(observer: Observer): void;
  detach(observer: Observer): void;
  notify(): void;
}

// 定义一个观察者接口
interface Observer {
  update(): void;
}

// 实现主题
class ConcreteSubject implements Subject {
  private observers: Observer[] = [];

  attach(observer: Observer): void {
    this.observers.push(observer);
  }

  detach(observer: Observer): void {
    const index = this.observers.indexOf(observer);
    if (index >= 0) {
      this.observers.splice(index, 1);
    }
  }

  notify(): void {
    for (const observer of this.observers) {
      observer.update();
    }
  }
}

// 实现观察者
class ConcreteObserver implements Observer {
  update(): void {
    console.log('观察者收到通知');
  }
}

// 使用观察者模式
const subject = new ConcreteSubject();
const observer = new ConcreteObserver();

subject.attach(observer);
subject.notify();

在项目中如何选择和应用设计模式

在项目中选择和应用设计模式时,我们可以按照以下步骤进行:

  1. 理解问题和需求:首先要清楚项目中存在的问题和需求,以确定是否需要使用设计模式。
  2. 研究和学习设计模式:通过学习设计模式的原理和使用方法,了解各种设计模式的适用场景和解决问题的方式。
  3. 评估项目情况:根据项目的规模、团队成员的技能和经验,以及项目的时间和资源限制,评估哪些设计模式适用于当前项目。
  4. 应用设计模式:在项目中根据需求和情况选择合适的设计模式,并按照设计模式的原则和最佳实践进行应用。

总之,设计模式是前端开发中重要的工具和技巧,可以提高代码的可读性、可维护性和可扩展性。熟悉并应用设计模式将使我们的代码更加优雅和高效。