探索前端开发中的设计模式 | 青训营

引言

在软件开发领域,设计模式是一种被广泛接受且经过验证的最佳实践方法。设计模式提供了解决常见问题的可复用方案,帮助程序员更好地组织和设计代码。本文旨在介绍设计模式的基本概念、作用以及常见的设计模式,并探讨它们在提高代码质量和开发效率方面的优势。

一、软件开发常见设计模式

  1. Singleton模式(单例模式):确保一个类只有一个实例,并提供一个全局访问点。
  2. Builder模式(建造者模式):用于创建复杂对象,通过一步一步的构建,将创建逻辑与表示分离。
  3. Factory模式(工厂模式):通过工厂类来创建对象,隐藏了对象的具体创建过程。
  4. Observer模式(观察者模式):定义了对象之间一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
  5. Strategy模式(策略模式):定义了一簇算法,并使其相互之间可以互换,使算法的变化独立于使用算法的客户端。
  6. Decorator模式(装饰者模式):动态地给对象添加额外的责任,可以在不改变其结构的情况下,增强对象的功能。

二、设计模式详细解析

Singleton模式(单例模式)

定义:单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

理解: 皇帝制度,只有一个皇帝执掌全局,一声令下所有相关地方都要变化

图解:

优点:全局只提供一个实例,便于管理,且避免重复创建相同对象的开销

缺点:难扩展,难以进行单元测试,违反单一职责原则,高度耦合

使用场景:需要做全局统一控制的场景

使用举例(前端方向):

  • 状态管理器 (Vuex,Redux)
    它将应用的状态存储在一个全局的 Store 对象中,并通过提供全局访问点的方式,使所有组件都能够访问和更新这个状态。
  • 全局事件总线 (bus)
    通过向全局创建一个事件总线对象$bus,其他组件可以通过订阅和发布事件来进行通信。
  • 配置管理器
    保证系统中只有一个配置文件对象,方便访问和管理配置信息。

Builder模式(建造者模式)

定义:建造者模式是一种创建型设计模式,它将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

理解:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

图解:

优点:封装性较强,可扩展性强,灵活构建和调用

缺点:增加了系统的复杂度,构建的对象要遵循一些步骤

使用场景:需要生成的对象有复杂的内部结构,部分属性相互依赖

代码举例(前端方向TS):

kotlin 复制代码
class ProductPageBuilder {
  constructor() {
    this.productImage = null;
    this.productName = "";
  }

  setProductImage(imageUrl) {
    this.productImage = imageUrl;
    return this;
  }

  setProductName(name) {
    this.productName = name;
    return this;
  }

  build() {
    const productPage = new ProductPage();
    productPage.setProductImage(this.productImage);
    productPage.setProductName(this.productName);
    return productPage;
  }
}

Factory模式(工厂模式)

定义:工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但具体实现由子类决定。工厂模式将对象的实例化过程推迟到子类中进行,从而避免了在客户端代码中直接使用具体类进行对象的创建。

分类:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

理解:批量的产出一些对象,不去关心具体的实现细节,就是根据模具(接口)生成即可,工厂类包含一个用于创建对象的方法,该方法会根据传入的参数或条件返回相应的具体对象实例。

图解:

优点:封装了对象的创建过程,可进行批量的创建,提供了可扩展性,降低了代码的耦合度

缺点:可能会增加系统的复杂度,增加内存的占用,在项目初期不太建议使用该模式

使用场景:

  • 客户端无需关心对象的实例化过程,只需要通过一个公共接口获取所需对象
  • 当需要通过不同的方式创建、初始化或配置对象时,可以使用工厂模式来封装这些复杂的创建过程。
  • 当系统需要扩展新的产品时,可以通过添加新的具体工厂类来实现,而无需修改已有代码。

使用举例(前端方向):

typescript 复制代码
// 定义一个组件工厂类
class ComponentFactory {
  // 创建组件的方法
  createComponent(type, props) {
    switch (type) {
      case 'button':
        return new ButtonComponent(props);
      case 'input':
        return new InputComponent(props);
      default:
        throw new Error('Invalid component type.');
    }
  }
}

// 定义一个按钮组件类
class ButtonComponent {
  constructor(props) {
    // 按钮组件的初始化逻辑
    this.text = props.text;
    this.onClick = props.onClick;
  }

  render() {
    // 按钮组件的渲染逻辑
    const button = document.createElement('button');
    button.innerText = this.text;
    button.addEventListener('click', this.onClick);
    return button;
  }
}

// 定义一个输入框组件类
class InputComponent {
  constructor(props) {
    // 输入框组件的初始化逻辑
    this.placeholder = props.placeholder;
    this.onInput = props.onInput;
  }

  render() {
    // 输入框组件的渲染逻辑
    const input = document.createElement('input');
    input.setAttribute('placeholder', this.placeholder);
    input.addEventListener('input', this.onInput);
    return input;
  }
}

Observer模式(观察者模式)

定义:观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

角色:观察者 与 被观察者

理解:观察者通过观察,来确定被观察者有哪些变化,前端写样式的时候,就是一种观察者模式的体现

图解:

优点:

  1. 松耦合:观察者模式将目标和观察者分离,使它们之间的依赖关系变得松散,目标对象不需要知道观察者的具体细节。
  2. 可扩展性:可以方便地增加新的观察者,因为目标对象只需要维护一个观察者列表,并通知所有观察者即可。
  3. 易于维护:观察者模式将逻辑分散到多个观察者中,每个观察者只负责处理自己感兴趣的事件,使代码更加清晰、易于维护。

缺点:

  1. 过多的通知:如果观察者较多或者观察者的处理逻辑较复杂,目标对象每次状态变化时需要通知所有观察者,可能会导致性能问题。
  2. 循环依赖:在设计观察者模式时,需要注意避免观察者和目标对象之间的循环依赖,否则可能导致系统崩溃或循环调用。

使用场景:

  1. 当一个对象的改变需要同时改变其他对象,且它不知道具体有多少个对象有待改变时
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时

使用举例(前端方向):

  1. 消息订阅与发布
  2. 通过代码看页面视图的更新

策略模式(Strategy Pattern)

定义:策略模式是一种行为设计模式,它定义了一系列的算法,并将它们封装起来,使它们可以互相替换。策略模式可以让算法的变化独立于使用算法的客户端。

理解:定义不同类型的方法,来实现不同场景下的同一个需求

图解:

角色:

  • Context(上下文):它是策略模式的核心,负责维护一个对策略对象的引用,并将具体的请求委托给策略对象来处理。
  • Strategy(策略):定义了一个公共接口,所有具体策略都必须实现这个接口。
  • Concrete Strategies(具体策略):实现了策略接口,提供了不同的算法实现。 优点:
  1. 算法的独立性:策略模式将算法封装成独立的策略类,使得它们可以独立地变化和复用,易于维护和扩展。
  2. 可替换性:由于策略模式将算法与使用者解耦,因此可以动态地切换不同的策略,满足不同的需求,而无需修改客户端代码。
  3. 简化条件语句:策略模式避免了使用大量的条件语句来选择不同的算法,使代码更加清晰、易于理解。

缺点:

  1. 增加类的数量:引入策略模式会增加类的数量,每个具体策略都需要一个对应的策略类。
  2. 客户端必须了解不同的策略:客户端在使用策略模式时,需要了解不同的策略,并选择合适的策略,增加了一定的使用复杂性。

使用场景:

  1. 当一个系统中有多个类只有一个行为不同的情况下,可以考虑使用策略模式,将这些行为封装到不同的策略类中。
  2. 当需要在运行时动态地选择算法时,可以使用策略模式。

使用举例(前端方向):普通登陆与第三方登录

Decorator模式(装饰者模式)

定义:装饰者模式是一种结构设计模式,它允许在不改变已有对象的情况下,动态地将新功能添加到对象上。

理解:给某个物品添加包装,物品本身不变,只是添加了一些修饰,让其更加美观

图解:

优点:

  1. 灵活性:装饰者模式允许在运行时动态地添加或移除功能,可以灵活地组合不同的装饰类,实现各种组合效果。
  2. 单一职责原则:装饰者模式将功能划分到不同的装饰类中,每个装饰类只关注自己特定的功能,符合单一职责原则,使代码更加清晰。
  3. 可扩展性:由于装饰者模式不依赖于继承关系,而是通过对象的包装来扩展功能,因此更易于扩展和维护。

缺点:

  1. 多层嵌套:使用过多的装饰者可能会导致类的数量增多,增加代码的复杂性。
  2. 顺序依赖:装饰者模式中装饰者的调用顺序是固定的,需要注意装饰者之间的相互影响和顺序依赖。

使用场景:

  1. 当需要动态地为对象添加或移除功能时
  2. 当不希望通过继承来扩展功能或生成子类时

使用举例:动态的给某个按钮添加样式

三、设计模式六大原则

  • 单一职责原则(Single Responsibility Principle)

解释 : 每个类应该只负责一个单一的功能或职责

  • 开闭原则(Open Closed Principle)

解释 : 软件实体(类、模块、函数等)应该对扩展是开放的,但对修改是封闭的

  • 里氏替换原则(Liskov Substitution Principle)

解释 : 子类应该能够替换掉父类并且不产生任何错误或异常,即任何可以使用父类对象的地方都可以使用子类对象

  • 最少知道原则(Law of Demeter)

解释 : 一个对象应该与其他对象保持最少的了解,即一个对象应该尽量减少与其他对象之间的交互,只与直接的朋友发生通信

  • 接口隔离原则(Interface Segregation Principle)

解释 : 客户端不应该依赖于它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上,尽量精简接口,避免臃肿的接口

  • 依赖倒置原则(Dependence Inversion Principle)

解释 : 高层模块不应该依赖于低层模块,它们都应该依赖于抽象接口。

抽象不应该依赖于具体实现细节,具体实现应该依赖于抽象

相关推荐
Find24 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记