Web 开发
2023-05-11

前端常用的设计模式

次点击
15分钟阅读

什么是设计模式

设计模式是在软件开发中经常出现的问题的解决方案。这些解决方案已被证明是有效的,并且在实践中被广泛应用,可以提高代码的可重用性、可维护性和可扩展性。设计模式通常由一组互相关联的类、对象和方法组成,每个模式都有一个特定的目的和使用场景,并使用一致的命名和结构来使其易于理解和使用。设计模式是从软件开发实践中总结出来的最佳实践,可以帮助开发人员更快地解决问题并编写高质量的代码。

设计原则

最重要的思想:开放封闭原则

  • 对扩展开放
  • 对修改封闭

常用的设计模式

工厂模式

JS 工厂模式是一种创建对象的设计模式,可以用来创建对象而无需明确的使用 new 运算符,下面是一个示例

function Person(name, age) {
 this.name = name;
 this.age = age
}
 
// 创建工厂函数,用于创建 Person 实例
function createPerson(name, age) {
	return new Person(name, age)
}
 
const person1 = createPerson('le', 1);
const person2 = createPerson('le', 2);

单例模式

单例模式保证一个类只有一个实例,并提供全局访问点,通过限制类的构造函数为私有来实现,以防止外部代码创建多个实例

class Singleton {
  private static instance: Singleton | null = null;
  private constructor() {}
  public static getInstance() {
    if (!this.instance) {
      this.instance = new Singleton();
    }
    return this.instance;
  }
}
 
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
 
console.log("instance1 === instance2", instance1 === instance2); // true

代理模式

代理模式通过创建一个代理对象来控制对另一个对象的访问,代理对象可以拦截并处理对原始对象的请求

class Image {
  constructor(url) {
    this.url = url;
  }
  
  draw() {
    console.log(`Drawing image from ${this.url}`);
  }
}
 
class ImageProxy {
  constructor(url) {
    this.image = new Image(url);
  }
  
  draw() {
    this.showPlaceholder();
    this.image.draw();
  }
  
  showPlaceholder() {
    console.log('Showing placeholder image');
  }
}
 
const img = new ImageProxy('https://example.com/img.jpg');
img.draw(); // 显示 placeholder, 然后加载并绘制图片

在上面的例子中,ImageProxy 对象充当了 Image 对象的代理,在实际绘制图片之前,它会先显示一个占位符。

观察者模式

其中一个对象(称为主题)维护其所有依赖对象列表(称为观察者),并在状态发生变化时通知这些观察者。这使得多个对象之间可以松散耦合,且当一个对象的状态发生更改时会自动更新相关的其他对象。

class Subject {
  private observers: Observer[];
  constructor() {
    this.observers = [];
  }
 
  subscribe(observer) {
    this.observers.push(observer);
  }
 
  unsubscribe(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  }
 
  notify(data) {
    this.observers.forEach((observer) => observer.update(data));
  }
}
 
class Observer {
  private name: string;
  constructor(name) {
    this.name = name;
  }
 
  update(data) {
    console.log(`${this.name} received data: ${data}`);
  }
}
 
const subject = new Subject();
 
const observerA = new Observer("Observer A");
const observerB = new Observer("Observer B");
 
subject.subscribe(observerA);
subject.subscribe(observerB);
 
subject.notify("Hello, world!");
// Observer A received data: Hello, world!
// Observer B received data: Hello, world!
 
subject.unsubscribe(observerB);
 
subject.notify("Goodbye!");
// Observer A received data: Goodbye!

在上面的代码中,Subject 对象维护了一个观察者列表(observers)。 Observer 对象可以订阅(subscribe)或取消订阅(unsubscribe)主题对象的通知。当 Subject 发生变化时,它会调用其所有观察者的 update 方法,并传递数据以供观察者使用。在这个例子中,我们创建了两个观察者(observerA 和 observerB),它们都订阅了 (subscribesubject 对象的通知。然后,subject 的 notify 方法被调用两次,观察者会接收到相应的数据并输出到控制台。最后,我们取消了其中一个观察者(observerB)的订阅,因此它不会再接收到后续的通知。

发布订阅模式

其中一个主题或事件(发布者)在发生时通知所有已经注册的监听器(订阅者)。这使得多个模块之间可以松散耦合且具有高度灵活性

class EventManager {
  private events: any;
  constructor() {
    this.events = {};
  }
 
  // 订阅事件
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
 
  // 发布事件
  publish(event, data) {
    if (!this.events[event]) {
      return;
    }
    this.events[event].forEach((callback) => {
      callback(data);
    });
  }
}
 
// 创建一个事件管理器实例
const eventManager = new EventManager();
 
// 订阅 "click" 事件
eventManager.subscribe("click", function (data) {
  console.log(`用户点击了 ${data}`);
});
 
// 发布 "click" 事件
eventManager.publish("click", "按钮");
// 输出: 用户点击了 按钮

上述示例中,我们创建了一个 EventManager 类来管理事件。它具有两个方法,即 subscribe 和 publish。其中,subscribe 方法允许我们为特定的事件添加回调函数,而 publish 方法则用于触发该事件并执行所有订阅的回调函数。

观察者模式和发布订阅模式的区别

观察者模式中,一个主题对象(subject)维护了一个依赖它的观察者对象(observer)列表,在状态变化时直接通知所有观察者进行更新,观察者模式是一种一对多的关系。

发布订阅模式中,消息的发布者(publisher)并不会直接通知订阅者(subscriber),而是通过一个中介对象(broker)来完成这个过程。发布者将消息发布到 broker 上,broker 根据预先定义好的规则将消息转发给相应的订阅者,发布订阅模式是一种多对多的关系

装饰器模式

装饰器模式允许在运行时给对象添加额外的行为(即装饰),而不需要修改原始对象的代码

// 原始对象
class Car {
  drive() {
    console.log("Car is driving");
  }
}
 
// 装饰器类
class Decorator {
  private car: Car;
  constructor(car) {
    this.car = car;
  }
 
  driveFast() {
    console.log("Driving fast...");
  }
 
  drive() {
    this.car.drive();
    this.driveFast();
  }
}
 
// 使用装饰器
const car = new Car();
const decoratedCar = new Decorator(car);
decoratedCar.drive(); // 输出 "Car is driving" 和 "Driving fast..."