前端常见的 8 种设计模式,你用过哪几种?

简介: 前端常见的 8 种设计模式,你用过哪几种?

一、设计模式是什么?

设计模式是在某种场合下对某个问题的一种解决方案。设计模式是通过概念总结出来的模版,总结出来的固定的东西。每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。


在前端开发中,设计模式是一种被广泛应用的思想。设计模式可以帮助开发者解决常见的问题,并提供可重用的解决方案。本文将会介绍前端常见的设计模式,并通过代码详解它们的实现。


二、设计原则

1. S – Single Responsibility Principle 单一职责原则

  • 一个程序只做好一件事
  • 如果功能过于复杂就拆分开,每个部分保持独立

2. O – OpenClosed Principle 开放/封闭原则

  • 对扩展开放,对修改封闭
  • 增加需求时,扩展新代码,而非修改已有代码

3. L – Liskov Substitution Principle 里氏替换原则

  • 子类能覆盖父类
  • 父类能出现的地方子类就能出现

4. I – Interface Segregation Principle 接口隔离原则

  • 保持接口的单一独立
  • 类似单一职责原则,这里更关注接口

5. D – Dependency Inversion Principle 依赖倒转原则

  • 面向接口编程,依赖于抽象而不依赖于具
  • 使用方只关注接口而不关注具体类的实现

三、设计模式的类型

1. 结构型模式(Structural Patterns):

       通过识别系统中组件间的简单关系来简化系统的设计。

2. 创建型模式(Creational Patterns):

     处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。

3. 行为型模式(Behavioral Patterns):

     用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。

四、前端常见的8种设计模式

1、单例模式

单例模式是指一个类只能被实例化一次,并提供全局访问点。这个模式非常适合那些需要共享资源的场景。比如,在前端开发中,我们经常需要确保某些资源只被加载一次,而不是每次都重新加载。下面是一个使用单例模式的例子:

class Singleton {
  constructor() {
    if (typeof Singleton.instance === 'object') {
      return Singleton.instance;
    }
    this.name = 'Singleton';
    Singleton.instance = this;
    return this;
  }
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true

上述代码中,我们创建了一个 Singleton 类,该类只能被实例化一次。如果多次尝试实例化该类,将会返回同一个实例。这样就保证了某些资源只会被加载一次,从而提高性能。

2、观察者模式

观察者模式是指当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。这个模式非常适合那些需要实时更新用户界面的场景。下面是一个使用观察者模式的例子:

class ObserverList {
  constructor() {
    this.observerList = [];
  }
  add(observer) {
    return this.observerList.push(observer);
  }
  remove(observer) {
    this.observerList = this.observerList.filter((obs) => obs !== observer);
  }
  count() {
    return this.observerList.length;
  }
  get(i) {
    return this.observerList[i];
  }
}
class Subject {
  constructor() {
    this.observers = new ObserverList();
  }
  addObserver(observer) {
    this.observers.add(observer);
  }
  removeObserver(observer) {
    this.observers.remove(observer);
  }
  notify(context) {
    const observerCount = this.observers.count();
    for (let i = 0; i < observerCount; i++) {
      this.observers.get(i).update(context);
    }
  }
}
class Observer {
  constructor() {
    this.update = () => {};
  }
}
const subject = new Subject();
const observer1 = new Observer();
observer1.update = function (context) {
  console.log(`Observer 1: ${context}`);
};
const observer2 = new Observer();
observer2.update = function (context) {
  console.log(`Observer 2: ${context}`);
};
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify('Hello World');

上述代码中,我们创建了一个 Subject 类和一个 Observer 类。Subject 类包含了一个观察者列表,并提供了添加、删除和通知观察者的方法。Observer 类则包含了一个更新方法,用于处理来自被观察者的通知。在主程序中,我们创建了两个观察者,并将它们添加到被观察者的观察者列表中。然后,我们通知被观察者,被观察者会自动通知所有观察者,并执行它们的更新方法。


3、工厂模式

工厂模式是指通过一个工厂类来创建其他类的实例。这个模式非常适合那些需要根据不同条件创建不同实例的场景。下面是一个使用工厂模式的例子:

class ProductA {
  constructor(name) {
    this.name = name;
  }
  operation() {
    console.log(`Product A (${this.name}) is working.`);
  }
}
class ProductB {
  constructor(name) {
    this.name = name;
  }
  operation() {
    console.log(`Product B (${this.name}) is working.`);
  }
}
class Factory {
  createProduct(type, name) {
    switch (type) {
      case 'A':
        return new ProductA(name);
      case 'B':
        return new ProductB(name);
      default:
        throw new Error('Invalid product type.');
    }
  }
}
const factory = new Factory();
const productA1 = factory.createProduct('A', 'productA1');
const productA2 = factory.createProduct('A', 'productA2');
const productB1 = factory.createProduct('B', 'productB1');
const productB2 = factory.createProduct('B', 'productB2');
productA1.operation(); // Product A (productA1) is working.
productA2.operation(); // Product A (productA2) is working.
productB1.operation(); // Product B (productB1) is working.
productB2.operation(); // Product B (productB2) is working.

上述代码中,我们创建了两个产品类 `ProductA` 和 `ProductB`,以及一个工厂类 `Factory`。工厂类提供了一个创建产品实例的方法 `createProduct`,该方法根据传入的参数来决定创建哪种产品实例。在主程序中,我们通过工厂类创建了四个不同的产品实例,并分别执行它们的操作方法。


4、装饰者模式

装饰者模式是指动态地给一个对象增加一些额外的功能。这个模式非常适合那些需要在运行时动态改变对象行为的场景。下面是一个使用装饰者模式的例子:

class Shape {
  draw() {
  }
}
class Circle extends Shape {
  draw() {
    console.log('Drawing a circle.');
  }
}
class Rectangle extends Shape {
  draw() {
    console.log('Drawing a rectangle.');
  }
}
class Decorator {
  constructor(shape) {
    this.shape = shape;
  }
  draw() {
    this.shape.draw();
  }
}
class RedShapeDecorator extends Decorator {
  draw() {
    this.shape.draw();
    this.setRedBorder();
  }
  setRedBorder() {
    console.log('Setting red border.');
  }
}
const circle = new Circle();
const rectangle = new Rectangle();
circle.draw(); // Drawing a circle.
rectangle.draw(); // Drawing a rectangle.
const redCircle = new RedShapeDecorator(new Circle());
const redRectangle = new RedShapeDecorator(new Rectangle());
redCircle.draw(); // Drawing a circle. Setting red border.
redRectangle.draw(); // Drawing a rectangle. Setting red border.

上述代码中,我们创建了两个形状类 Circle 和 Rectangle,以及一个装饰者类 Decorator。装饰者类包含了一个形状对象,用于对其进行装饰。然后,我们创建了一个红色形状装饰者类 RedShapeDecorator,用于在形状周围添加一个红色边框。在主程序中,我们先执行原始形状的绘制方法,然后再使用红色装饰器对其进行装饰。


5、代理模式

代理模式是指使用一个代理对象来控制对另一个对象的访问。这个模式非常适合那些需要控制对某些敏感资源的访问的场景。下面是一个使用代理模式的例子:

class Image {
  constructor(url) {
    this.url = url;
    this.loadImage();
  }
  loadImage() {
    console.log(`Loading image from ${this.url}`);
  }
}
class ProxyImage {
  constructor(url) {
    this.url = url;
  }
  loadImage() {
    if (!this.image) {
      this.image = new Image(this.url);
    }
    console.log(`Displaying cached image from ${this.url}`);
  }
}
const image1 = new Image('https://example.com/image1.jpg');
const proxyImage1 = new ProxyImage('https://example.com/image1.jpg');
proxyImage1.loadImage(); // Loading image from https://example.com/image1.jpg
proxyImage1.loadImage(); // Displaying cached image from https://example.com/image1.jpg
const image2 = new Image('https://example.com/image2.jpg');
const proxyImage2 = new ProxyImage('https://example.com/image2.jpg');
proxyImage2.loadImage(); // Loading image from https://example.com/image2.jpg
proxyImage2.loadImage(); // Displaying cached image from https://example.com/image2.jpg

上述代码中,我们创建了一个图片类 `Image` 和一个代理图片类 `ProxyImage`。代理图片类包含了一个图片对象,用于控制对其加载和显示的访问。在主程序中,我们首先创建了一个真实的图片对象,并使用代理图片对象进行访问。第一次访问时,代理图片对象会加载并显示真实的图片;第二次访问时,代理图片对象直接从缓存中获取并显示图片。

6、适配器模式

适配器模式是指将不兼容接口的对象转化为兼容接口的对象。这个模式非常适合那些需要改变接口而不影响原有代码的场景。下面是一个使用适配器模式的例子:

class OldCalculator {
  operations(a, b, operation) {
    switch (operation) {
      case 'add':
        return a + b;
      case 'sub':
        return a - b;
      default:
        return NaN;
    }
  }
}
class NewCalculator {
  add(a, b) {
    return a + b;
  }
  sub(a, b) {
    return a - b;
  }
}
class CalculatorAdapter {
  constructor() {
    this.newCalculator = new NewCalculator();
  }
  operations(a, b, operation) {
    switch (operation) {
      case 'add':
        return this.newCalculator.add(a, b);
      case 'sub':
        return this.newCalculator.sub(a, b);
      default:
        return NaN;
    }
  }
}
const oldCalculator = new OldCalculator();
console.log(oldCalculator.operations(10, 5, 'add')); // 15
const newCalculator = new NewCalculator();
console.log(newCalculator.add(10, 5)); // 15
const calculatorAdapter = new CalculatorAdapter();
console.log(calculatorAdapter.operations(10, 5, 'add')); // 15

上述代码中,我们创建了一个旧计算器类 OldCalculator 和一个新计算器类 NewCalculator。然后,我们创建了一个适配器类 CalculatorAdapter,该类包含了一个新计算器对象,用于将旧计算器的操作转化为新计算器的操作。在主程序中,我们分别使用旧计算器、新计算器和适配器进行加法运算,并得到了相同的结果。

7、命令模式

命令模式是指将请求封装成一个对象,并提供与执行该请求相关的所有信息。这个模式非常适合那些需要执行多个不同操作的场景。下面是一个使用命令模式的例子:

class Receiver {
  run() {
    console.log('Receiver is running.');
  }
}
class Command {
  constructor(receiver) {
    this.receiver = receiver;
  }
  execute() {}
}
class StartCommand extends Command {
  execute() {
    this.receiver.run();
  }
}
class Invoker {
  setCommand(command) {
    this.command = command;
  }
  executeCommand() {
    this.command.execute();
  }
}
const receiver = new Receiver();
const startCommand = new StartCommand(receiver);
const invoker = new Invoker();
invoker.setCommand(startCommand);
invoker.executeCommand(); // Receiver is running.

上述代码中,我们创建了一个接收者类 Receiver 和一个命令基类 Command。然后,我们创建了一个具体命令类 StartCommand,该类继承自命令基类,并实现了执行方法 execute,用于启动接收者。最后,我们创建了一个调用者类 Invoker,该类包含了一个命令对象,并提供了执行命令的方法。在主程序中,我们创建了一个接收者对象、一个具体命令对象和一个调用者对象,并将具体命令对象设置为调用者对象的命令。然后,我们执行调用者对象的执行方法,该方法会调用具体命令对象的执行方法,从而启动接收者。这个例子比较简单,但命令模式可以应用于很多复杂的场景,例如撤销/恢复操作、事务管理等。

8、观察者模式

观察者模式是指在对象之间定义一种一对多的依赖关系,使得每当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。这个模式非常适合那些需要在应用中实现事件处理的场景。下面是一个使用观察者模式的例子:

class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index >= 0) {
      this.observers.splice(index, 1);
    }
  }
  notifyObservers() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }
}
class ConcreteSubject extends Subject {
  constructor(state) {
    super();
    this.state = state;
  }
  getState() {
    return this.state;
  }
  setState(state) {
    this.state = state;
    this.notifyObservers();
  }
}
class Observer {
  update() {}
}
class ConcreteObserver extends Observer {
  update(subject) {
    console.log(`The subject has changed to ${subject.getState()}.`);
  }
}
const subject = new ConcreteSubject('state1');
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.setState('state2'); // The subject has changed to state2.

上述代码中,我们创建了一个主题基类 Subject 和一个具体主题类 ConcreteSubject。主题类包含了一个状态属性和一组观察者对象,并提供了方法以添加、删除和通知观察者。然后,我们创建了一个观察者基类 Observer 和一个具体观察者类 ConcreteObserver,该类继承自观察者基类,并实现了更新方法。在主程序中,我们创建了一个具体主题对象、两个具体观察者对象,并将观察者对象添加到主题对象中。然后,我们修改了主题对象的状态,并通过主题对象通知观察者对象进行更新操作。


以上就是八种常见的设计模式以及它们的应用场景和示例代码。当然,这只是冰山一角,还有很多其他的设计模式可以应用于不同的场景。熟悉各种设计模式并且能够灵活运用它们可以让你成为更优秀的开发者。

目录
相关文章
|
8月前
|
设计模式 前端开发 开发者
探索现代前端开发中的设计模式
在现代前端开发中,设计模式是一种重要的工具,它可以帮助开发者提高代码的可维护性、可扩展性和可重用性。本文将介绍几种常见的设计模式,并探讨它们在前端开发中的应用。
|
8月前
|
设计模式 存储 缓存
精进前端开发:深入探讨前端设计模式
精进前端开发:深入探讨前端设计模式
76 0
|
8月前
|
设计模式 前端开发 算法
前端工程中的设计模式应用(下)
前端工程中的设计模式应用(下)
|
4天前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
|
10天前
|
设计模式 存储 供应链
前端必须掌握的设计模式——观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,实现了一种订阅机制。它包含两个角色:**观察者**(订阅消息、接收通知并执行操作)和**被观察者**(维护观察者列表、发送通知)。两者通过一对多的关系实现解耦,当被观察者状态改变时,会通知所有订阅的观察者。例如,商店老板作为被观察者,记录客户的需求并在商品到货时通知他们。前端应用中,如DOM事件注册、MutationObserver等也体现了这一模式。
|
23天前
|
设计模式 前端开发 调度
前端必须掌握的设计模式——工厂模式
工厂模式是一种创建型设计模式,通过工厂媒介提供统一接口来创建对象,客户端只需告知创建需求,具体逻辑由工厂处理。工厂模式分为简单工厂、标准工厂和抽象工厂,分别适用于不同场景下的对象创建需求。简单工厂利用静态方法创建对象,标准工厂通过具体工厂类减少耦合,抽象工厂则用于创建一系列相关或依赖对象的家族。
|
3天前
|
设计模式 存储 缓存
前端必须掌握的设计模式——策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,旨在将多分支复杂逻辑解耦。每个分支类只关心自身实现,无需处理策略切换。它避免了大量if-else或switch-case代码,符合开闭原则。常见应用场景包括表单验证、风格切换和缓存调度等。通过定义接口和上下文类,策略模式实现了灵活的逻辑分离与扩展。例如,在国际化需求中,可根据语言切换不同的词条包,使代码更加简洁优雅。总结来说,策略模式简化了多条件判断,提升了代码的可维护性和扩展性。
|
8天前
|
设计模式 消息中间件 供应链
前端必须掌握的设计模式——发布订阅模式
发布订阅模式(Publish-Subscribe Pattern)是一种设计模式,类似于观察者模式,但通过引入第三方中介实现发布者和订阅者的解耦。发布者不再直接通知订阅者,而是将消息发送给中介,由中介负责分发给订阅者。这种方式提高了异步支持和安全性,适合复杂、高并发场景,如消息队列和流处理系统。代码实现中,通过定义发布者、订阅者和中介接口,确保消息的正确传递。此模式在前端开发中广泛应用,例如Vue的数据双向绑定。
|
21天前
|
设计模式 前端开发 JavaScript
前端必须掌握的设计模式——装饰器模式
装饰器模式是一种结构型设计模式,通过创建新类来包装原始对象,实现在不修改原有结构的前提下扩展新行为。其核心在于“组合”思想,使新功能可“即插即拔”。该模式具有解耦性、灵活性和动态性等特点,广泛应用于类的面向对象编程语言中,如JavaScript的注解和TypeScript的写法。示例中,通过装饰器模式为游戏角色动态添加装备,展示了其强大的扩展性和灵活性。
|
14天前
|
设计模式 前端开发 数据安全/隐私保护
前端必须掌握的设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过引入“替身”对象来间接访问真实对象,从而解耦并提升性能和安全性。例如,知名艺人复出后,经纪人作为代理筛选商单,确保只处理符合团队利益的请求。代码实现中,定义接口`IService`,艺人和经纪人都实现该接口,经纪人在访问时进行过滤和转发。代理模式常用于权限控制、性能优化等场景,如前端中的Tree-shaking和ES6的Proxy构造方法。
前端必须掌握的设计模式——代理模式