看完你还能记住几个设计模式?

一、设计思想与原则(SOLID)

1. 单一职责原则(Single Responsibility Principle,SRP)

核心思想:一个东西只干一件事。

例子

  • 错误:一个 LoginButton 组件既处理点击事件,又发网络请求,还显示成功动画。
  • 正确:拆成 LoginButton(只管显示和点击)、AuthService(发请求)、SuccessAnimation(播放动画),各干各的。

前端理解:就像 CSS、HTML、JS 分开管样式、结构、逻辑一样,一个组件别啥都干。


2. 开放/封闭原则(Open/Closed Principle,OCP)

核心思想:代码欢迎新功能加入,但不爱改老代码。

例子

  • 错误:有个 ProductCard 组件,每次加新字段(价格、折扣)都改它的代码。
  • 正确:设计一个 ProductCard 基组件,然后用 props 或插槽(slot)扩展功能,比如 <ProductCard><Price /><Discount /></ProductCard>,新需求只加新组件。

前端理解 :就像用 React 的 props 或 Vue 的 slot,新东西插进来,老代码不动。


3. 里氏替换原则(Liskov Substitution Principle,LSP)

核心思想:子类能无缝替换父类,不出乱子。

例子

  • 错误:Button 是单机触发,FancyButton 改成双击触发,用它替换 Button 时功能就不对了。
  • 正确:FancyButton 继承 Button,保留单机触发,只加动画效果,替换时没问题。

前端理解:就像 CSS 继承,子元素别把父元素的样式完全颠覆,保持一致性。


4. 接口隔离原则(Interface Segregation Principle,ISP)

核心思想:别强迫用东西的人接受不需要的功能。

例子

  • 错误:一个 UserAPI 接口塞了所有用户相关方法,组件用时得全接。
  • 正确:拆成 AuthAPIloginlogout)和 ProfileAPIgetProfilesendEmail),组件按需引入。

前端理解 :就像组件的 props,只传用得上的,别一股脑全塞进去。


5. 依赖反转原则(Dependency Inversion Principle,DIP)

核心思想:高层别直接依赖底层,大家都依赖抽象。

例子

  • 错误:LoginForm 里写死 fetchLogin(),换成 axiosLogin() 得改组件。
  • 正确:定义个 loginService 接口,LoginForm 只管调用 loginService.login(),具体用 fetch 还是 axios 通过 props 传进来。

前端理解 :就像 React 的 useContext 或 Vue 的 provide/inject,组件不关心数据咋来的,只管用。


二、常用设计模式

1. 单例模式

  • 定义: 确保一个类只有一个实例,并提供全局访问点

  • 应用场景:

    • 需要全局唯一对象时,如配置管理器、日志记录器、数据库连接池。
    • 避免资源浪费或状态冲突
typescript 复制代码
class Singleton {
  private static instance: Singleton | null = null;
  constructor(){}
  public static getInstance(){
    if(this.instance === null){
      this.instance = new Singleton()
    }
    return this.instance
  }
}

2. 工厂模式

  • 定义: 通过工厂类创建对象,隐藏对象创建的细节,提供统一的创建接口。

  • 应用场景:

    • 对象创建复杂或需要根据条件动态选择类型时,如UI组件库、数据库驱动实例化。
    • 解耦对象创建与使用。
typescript 复制代码
// 1. 定义产品接口
interface WebComponent {
  render(): string;
}
​
// 2. 具体产品类 - 按钮
class Button implements WebComponent {
  private label: string;
​
  constructor(label: string) {
    this.label = label;
  }
​
  render(): string {
    return `<button>${this.label}</button>`;
  }
}
​
// 3. 具体产品类 - 输入框
class Input implements WebComponent {
  private placeholder: string;
​
  constructor(placeholder: string) {
    this.placeholder = placeholder;
  }
​
  render(): string {
    return `<input type="text" placeholder="${this.placeholder}" />`;
  }
}
​
// 4. 工厂类 - 负责创建组件
class ComponentFactory {
  static createComponent(type: string, props: string): WebComponent {
    switch (type.toLowerCase()) {
      case "button":
        return new Button(props);
      case "input":
        return new Input(props);
      default:
        throw new Error(`Unknown component type: ${type}`);
    }
  }
}
​
// 5. 使用示例
function main() {
  // 通过工厂创建按钮
  const button = ComponentFactory.createComponent("button", "Click Me");
  console.log(button.render()); // 输出: <button>Click Me</button>
​
  // 通过工厂创建输入框
  const input = ComponentFactory.createComponent("input", "Enter text here");
  console.log(input.render()); // 输出: <input type="text" placeholder="Enter text here" />
}
​
main();

3. 发布订阅模式

  • 定义: 发布者发出事件,订阅者接收事件,通过事件中心解耦两者关系

  • 应用场景:

    • 异步通信,如消息队列、事件总线。
    • Web开发中的DOM事件监听、实时通知系统。
typescript 复制代码
class EventEmitter {
  private listeners: { [key: string]: { cb: Function; priority: number }[] };
  constructor() {
    this.listeners = {};
  }
  on(type, cb: Function, options) {
    if (typeof type !== "string" || typeof cb !== "function") {
      return false;
    }
    this.listeners[type] = this.listeners[type] || [];
    this.listeners[type].push({
      cb,
      priority: options.priority,
    });
    return () => {
      this.listeners[type] = this.listeners[type].filter(
        (item) => item.cb !== cb
      );
    };
  }
  emit(type, data) {
    if (typeof type !== "string") {
      return false;
    }
    if (this.listeners[type]) {
      this.listeners[type]
        .sort((a, b) => a.priority - b.priority)
        .forEach((item) => {
          item.cb.call(null, data);
        });
    }
  }
}

4. 观察者模式

  • 定义: 对象(主题)维护一组观察者,当主题状态变化时通知所有观察者。

  • 应用场景:

    • 一对多依赖关系,如UI控件状态更新、数据模型变化通知视图。
    • 与发布订阅类似,但更直接绑定主题与观察者。
typescript 复制代码
// 1. 定义观察者接口
interface Observer {
  update(news: string): void;
}
​
// 2. 定义主题接口
interface Subject {
  attach(observer: Observer): void; // 添加观察者
  detach(observer: Observer): void; // 移除观察者
  notify(): void; // 通知所有观察者
}
​
// 3. 具体主题类 - 新闻发布者
class NewsAgency implements Subject {
  private observers: Observer[] = [];
  private latestNews: string = "";
​
  // 添加观察者
  attach(observer: Observer): void {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer);
    }
  }
​
  // 移除观察者
  detach(observer: Observer): void {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }
​
  // 通知所有观察者
  notify(): void {
    for (const observer of this.observers) {
      observer.update(this.latestNews);
    }
  }
​
  // 发布新新闻并触发通知
  publishNews(news: string): void {
    this.latestNews = news;
    this.notify();
  }
}
​
// 4. 具体观察者类 - 订阅者
class Subscriber implements Observer {
  private name: string;
​
  constructor(name: string) {
    this.name = name;
  }
​
  update(news: string): void {
    console.log(`${this.name} 收到新闻: ${news}`);
  }
}
​
// 5. 使用示例
function main() {
  // 创建新闻发布者
  const newsAgency = new NewsAgency();
​
  // 创建订阅者
  const sub1 = new Subscriber("张三");
  const sub2 = new Subscriber("李四");
  const sub3 = new Subscriber("王五");
​
  // 订阅者订阅新闻
  newsAgency.attach(sub1);
  newsAgency.attach(sub2);
  newsAgency.attach(sub3);
​
  // 发布新闻
  newsAgency.publishNews("明天放假啦!");
  // 输出:
  // 张三 收到新闻: 明天放假啦!
  // 李四 收到新闻: 明天放假啦!
  // 王五 收到新闻: 明天放假啦!
​
  // 移除一个订阅者
  newsAgency.detach(sub2);
​
  // 再次发布新闻
  newsAgency.publishNews("后天加班哦!");
  // 输出:
  // 张三 收到新闻: 后天加班哦!
  // 王五 收到新闻: 后天加班哦!
}
​
main();

5. 代理模式

  • 定义: 为目标对象提供一个代理,控制对目标的访问。

  • 应用场景:

    • 延迟加载(如图片懒加载)、权限控制、缓存代理。
    • Web开发中常见的API请求拦截。
csharp 复制代码
interface IUserDao {
  save(): void;
}
/**
 * 接口实现
 * 目标对象
 */
class UserDao implements IUserDao {
  public save(): void {
    console.log("----已经保存数据!----");
  }
}
​
/**
 * 代理对象,静态代理
 */
class UserDaoProxy implements IUserDao {
  //接收保存目标对象
  private target: IUserDao;
  constructor(target: IUserDao) {
    this.target = target;
  }
  public save(): void {
    this.target.save(); //执行目标对象的方法
  }
}

6. 策略模式

  • 定义: 定义一系列算法,封装并使它们可互换,客户端动态选择策略。

  • 应用场景:

    • 动态切换行为,如排序算法选择、支付方式切换。
    • 避免大量条件语句。
typescript 复制代码
// 1. 定义策略接口
interface PaymentStrategy {
  pay(amount: number): string;
}
​
// 2. 具体策略类 - 支付宝支付
class AlipayStrategy implements PaymentStrategy {
  pay(amount: number): string {
    return `使用支付宝支付 ${amount} 元`;
  }
}
​
// 3. 具体策略类 - 微信支付
class WechatStrategy implements PaymentStrategy {
  pay(amount: number): string {
    return `使用微信支付 ${amount} 元`;
  }
}
​
// 4. 具体策略类 - 信用卡支付
class CreditCardStrategy implements PaymentStrategy {
  pay(amount: number): string {
    return `使用信用卡支付 ${amount} 元`;
  }
}
​
// 5. 上下文类 - 支付处理器
class PaymentProcessor {
  private strategy: PaymentStrategy;
​
  constructor(strategy: PaymentStrategy) {
    this.strategy = strategy;
  }
​
  // 设置策略
  setStrategy(strategy: PaymentStrategy): void {
    this.strategy = strategy;
  }
​
  // 执行支付
  processPayment(amount: number): string {
    return this.strategy.pay(amount);
  }
}
​
// 6. 使用示例
function main() {
  // 创建支付处理器,默认使用支付宝
  const processor = new PaymentProcessor(new AlipayStrategy());
  
  // 使用支付宝支付
  console.log(processor.processPayment(100)); 
  // 输出: 使用支付宝支付 100 元
​
  // 切换到微信支付
  processor.setStrategy(new WechatStrategy());
  console.log(processor.processPayment(200)); 
  // 输出: 使用微信支付 200 元
​
  // 切换到信用卡支付
  processor.setStrategy(new CreditCardStrategy());
  console.log(processor.processPayment(300)); 
  // 输出: 使用信用卡支付 300 元
}
​
main();

7. 命令模式

  • 定义: 将请求封装为对象,支持撤销、重做等操作。

  • 应用场景:

    • 操作队列,如文本编辑器的撤销/重做。
    • 任务调度、远程控制系统。
kotlin 复制代码
// 1. 定义命令接口
interface Command {
  execute(): void;
  undo(): void;
}
​
// 2. 接收者 - 文本编辑器
class TextEditor {
  private content: string = "";
​
  append(text: string): void {
    this.content += text;
    console.log(`当前内容: ${this.content}`);
  }
​
  delete(length: number): void {
    this.content = this.content.slice(0, -length);
    console.log(`当前内容: ${this.content}`);
  }
​
  getContent(): string {
    return this.content;
  }
}
​
// 3. 具体命令类 - 添加文本命令
class AppendCommand implements Command {
  private editor: TextEditor;
  private text: string;
​
  constructor(editor: TextEditor, text: string) {
    this.editor = editor;
    this.text = text;
  }
​
  execute(): void {
    this.editor.append(this.text);
  }
​
  undo(): void {
    this.editor.delete(this.text.length);
  }
}
​
// 4. 具体命令类 - 删除文本命令
class DeleteCommand implements Command {
  private editor: TextEditor;
  private deletedText: string;
​
  constructor(editor: TextEditor, length: number) {
    this.editor = editor;
    this.deletedText = this.editor.getContent().slice(-length);
  }
​
  execute(): void {
    this.editor.delete(this.deletedText.length);
  }
​
  undo(): void {
    this.editor.append(this.deletedText);
  }
}
​
// 5. 调用者 - 命令管理器
class CommandManager {
  private history: Command[] = [];
  private undoStack: Command[] = [];
​
  execute(command: Command): void {
    command.execute();
    this.history.push(command);
    this.undoStack = []; // 每次新命令清空撤销栈
  }
​
  undo(): void {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undoStack.push(command);
    } else {
      console.log("没有可撤销的操作");
    }
  }
​
  redo(): void {
    const command = this.undoStack.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    } else {
      console.log("没有可重做的操作");
    }
  }
}
​
// 6. 使用示例
function main() {
  const editor = new TextEditor();
  const manager = new CommandManager();
​
  // 执行添加命令
  const appendCmd1 = new AppendCommand(editor, "Hello");
  manager.execute(appendCmd1); // 输出: 当前内容: Hello
​
  const appendCmd2 = new AppendCommand(editor, " World");
  manager.execute(appendCmd2); // 输出: 当前内容: Hello World
​
  // 执行删除命令
  const deleteCmd = new DeleteCommand(editor, 5);
  manager.execute(deleteCmd); // 输出: 当前内容: Hello
​
  // 撤销操作
  manager.undo(); // 输出: 当前内容: Hello World
  manager.undo(); // 输出: 当前内容: Hello
​
  // 重做操作
  manager.redo(); // 输出: 当前内容: Hello World
}
​
main();

8. 职责链模式

  • 定义: 将请求沿处理者链传递,直到某个处理者处理它。

  • 应用场景:

    • 请求分级处理,如HTTP请求过滤器、日志级别处理。
    • 解耦发送者和接收者。
scala 复制代码
// 1. 定义请求接口
interface Request {
  amount: number;
  description: string;
}
​
// 2. 定义抽象处理者类
abstract class Approver {
  protected next: Approver | null = null;
​
  // 设置下一个处理者
  setNext(approver: Approver): Approver {
    this.next = approver;
    return approver;
  }
​
  // 处理请求的抽象方法
  abstract handleRequest(request: Request): void;
}
​
// 3. 具体处理者 - 经理
class Manager extends Approver {
  handleRequest(request: Request): void {
    if (request.amount <= 1000) {
      console.log(`经理批准了请求: ${request.description}, 金额: ${request.amount} 元`);
    } else if (this.next) {
      console.log("经理无权批准,传递给下一级...");
      this.next.handleRequest(request);
    } else {
      console.log("无更高权限处理此请求");
    }
  }
}
​
// 4. 具体处理者 - 总监
class Director extends Approver {
  handleRequest(request: Request): void {
    if (request.amount <= 5000) {
      console.log(`总监批准了请求: ${request.description}, 金额: ${request.amount} 元`);
    } else if (this.next) {
      console.log("总监无权批准,传递给下一级...");
      this.next.handleRequest(request);
    } else {
      console.log("无更高权限处理此请求");
    }
  }
}
​
// 5. 具体处理者 - CEO
class CEO extends Approver {
  handleRequest(request: Request): void {
    if (request.amount <= 10000) {
      console.log(`CEO批准了请求: ${request.description}, 金额: ${request.amount} 元`);
    } else {
      console.log("金额过大,CEO也无法批准");
    }
  }
}
​
// 6. 使用示例
function main() {
  // 创建处理者
  const manager = new Manager();
  const director = new Director();
  const ceo = new CEO();
​
  // 设置职责链: 经理 -> 总监 -> CEO
  manager.setNext(director).setNext(ceo);
​
  // 测试不同金额的请求
  const request1: Request = { amount: 500, description: "购买办公用品" };
  manager.handleRequest(request1);
  // 输出: 经理批准了请求: 购买办公用品, 金额: 500 元
​
  const request2: Request = { amount: 3000, description: "团队聚餐" };
  manager.handleRequest(request2);
  // 输出:
  // 经理无权批准,传递给下一级...
  // 总监批准了请求: 团队聚餐, 金额: 3000 元
​
  const request3: Request = { amount: 15000, description: "采购服务器" };
  manager.handleRequest(request3);
  // 输出:
  // 经理无权批准,传递给下一级...
  // 总监无权批准,传递给下一级...
  // 金额过大,CEO也无法批准
}
​
main();

9. 装饰器模式

  • 定义: 动态为对象添加功能,不改变原有类结构。

  • 应用场景:

    • 扩展功能,如Web中间件、UI组件增强。
    • 替代继承以避免类爆炸。
typescript 复制代码
// 1. 定义组件接口
interface Coffee {
  getDescription(): string;
  getCost(): number;
}

// 2. 基础组件 - 简单咖啡
class SimpleCoffee implements Coffee {
  getDescription(): string {
    return "简单咖啡";
  }

  getCost(): number {
    return 5;
  }
}

// 3. 抽象装饰器类
abstract class CoffeeDecorator implements Coffee {
  protected coffee: Coffee;

  constructor(coffee: Coffee) {
    this.coffee = coffee;
  }

  getDescription(): string {
    return this.coffee.getDescription();
  }

  getCost(): number {
    return this.coffee.getCost();
  }
}

// 4. 具体装饰器 - 牛奶
class MilkDecorator extends CoffeeDecorator {
  constructor(coffee: Coffee) {
    super(coffee);
  }

  getDescription(): string {
    return `${this.coffee.getDescription()}, 加牛奶`;
  }

  getCost(): number {
    return this.coffee.getCost() + 2;
  }
}

// 5. 具体装饰器 - 糖
class SugarDecorator extends CoffeeDecorator {
  constructor(coffee: Coffee) {
    super(coffee);
  }

  getDescription(): string {
    return `${this.coffee.getDescription()}, 加糖`;
  }

  getCost(): number {
    return this.coffee.getCost() + 1;
  }
}

// 6. 使用示例
function main() {
  // 基础咖啡
  let coffee: Coffee = new SimpleCoffee();
  console.log(`${coffee.getDescription()} - 价格: ${coffee.getCost()} 元`);
  // 输出: 简单咖啡 - 价格: 5 元

  // 加牛奶
  coffee = new MilkDecorator(coffee);
  console.log(`${coffee.getDescription()} - 价格: ${coffee.getCost()} 元`);
  // 输出: 简单咖啡, 加牛奶 - 价格: 7 元

  // 再加糖
  coffee = new SugarDecorator(coffee);
  console.log(`${coffee.getDescription()} - 价格: ${coffee.getCost()} 元`);
  // 输出: 简单咖啡, 加牛奶, 加糖 - 价格: 8 元
}

main();

10. 享元模式

  • 定义: 通过共享对象减少内存使用,适用于大量相似对象。

  • 应用场景:

    • 大量重复对象管理,如游戏中的精灵、文本编辑器中的字符渲染。
    • 优化性能和内存。
typescript 复制代码
// 1. 定义享元接口
interface Icon {
  draw(x: number, y: number): void;
}

// 2. 享元类 - 图标
class IconFlyweight implements Icon {
  private name: string; // 内在状态(共享)

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

  draw(x: number, y: number): void {
    console.log(`绘制 ${this.name} 图标于位置 (${x}, ${y})`);
  }
}

// 3. 享元工厂 - 管理共享对象
class IconFactory {
  private icons: { [key: string]: IconFlyweight } = {};

  getIcon(name: string): IconFlyweight {
    if (!this.icons[name]) {
      this.icons[name] = new IconFlyweight(name);
      console.log(`创建新图标: ${name}`);
    }
    return this.icons[name];
  }

  getIconCount(): number {
    return Object.keys(this.icons).length;
  }
}

// 4. 客户端 - 使用享元
class IconRenderer {
  private factory: IconFactory;

  constructor(factory: IconFactory) {
    this.factory = factory;
  }

  renderIcons(): void {
    // 使用相同的图标对象在不同位置绘制
    const starIcon = this.factory.getIcon("star");
    starIcon.draw(10, 20);
    starIcon.draw(30, 40);

    const circleIcon = this.factory.getIcon("circle");
    circleIcon.draw(50, 60);

    const starIconAgain = this.factory.getIcon("star"); // 复用已有的 star
    starIconAgain.draw(70, 80);

    console.log(`总共创建的图标数量: ${this.factory.getIconCount()}`);
  }
}

// 5. 使用示例
function main() {
  const factory = new IconFactory();
  const renderer = new IconRenderer(factory);

  renderer.renderIcons();
  // 输出:
  // 创建新图标: star
  // 绘制 star 图标于位置 (10, 20)
  // 绘制 star 图标于位置 (30, 40)
  // 创建新图标: circle
  // 绘制 circle 图标于位置 (50, 60)
  // 绘制 star 图标于位置 (70, 80)
  // 总共创建的图标数量: 2
}

main();

三、资料

很好的讲设计模式的博客

如果文章对你有用可以点赞收藏哦!这对我也很重要 ❤️

相关推荐
南部余额3 分钟前
playwright解决重复登录问题,通过pytest夹具自动读取storage_state用户状态信息
前端·爬虫·python·ui·pytest·pylawright
十五年专注C++开发13 分钟前
设计模式之适配器模式(二):STL适配器
c++·设计模式·stl·适配器模式·包装器
前端与小赵13 分钟前
webpack和vite之间的区别
前端·webpack·vite
zy01010117 分钟前
React受控表单绑定
前端·javascript·react.js·mvvm·双向数据绑定
百锦再17 分钟前
React编程的核心概念:数据流与观察者模式
前端·javascript·vue.js·观察者模式·react.js·前端框架·ecmascript
2401_8724878819 分钟前
网络安全之前端学习(css篇2)
前端·css·学习
SuperYing29 分钟前
前端候选人突围指南:让面试官主动追着要简历的五大特质(个人总结版)
前端·面试
前端双越老师32 分钟前
我的编程经验与认知
前端
linweidong38 分钟前
前端Three.js面试题及参考答案
前端·javascript·vue.js·typescript·前端框架·three.js·前端面经
GISer_Jing1 小时前
前端常问的宏观“大”问题详解(二)
linux·前端·ubuntu