阅读视图

发现新文章,点击刷新页面。

设计模式-行为型

设计模式-行为型

本文主要介绍下行为型设计模式,包括策略模式模板方法模式观察者模式迭代器模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式,提供前端场景和 ES6 代码的实现过程。 供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

本文主要介绍下行为型设计模式,包括策略模式模板方法模式观察者模式迭代器模式责任链模式命令模式备忘录模式状态模式访问者模式中介者模式解释器模式,提供前端场景和 ES6 代码的实现过程。

什么是行为型

行为型模式(Behavioral Patterns)主要关注对象之间的通信职责分配。这些模式描述了对象之间如何协作共同完成任务,以及如何分配职责。行为型模式不仅关注类和对象的结构,更关注它们之间的相互作用,通过定义清晰的通信机制,解决系统中复杂的控制流问题,使得代码更加清晰、灵活和易于维护。

行为型模式

策略模式(Strategy)

策略模式(Strategy Pattern)定义一系列的算法,把它们一个个封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户而变化。

前端中的策略模式场景

  • 表单验证:将不同的验证规则(如非空、邮箱格式、手机号格式)封装成策略,根据需要选择验证策略。
  • 不同业务逻辑处理:例如,根据用户权限(普通用户、VIP、管理员)展示不同的 UI 或执行不同的逻辑。
  • 缓动动画算法:在动画库中,提供多种缓动函数(如 linearease-inease-out)供用户选择。

策略模式-JS实现

// 策略对象
const strategies = {
  "S": (salary) => salary * 4,
  "A": (salary) => salary * 3,
  "B": (salary) => salary * 2
};

// 环境类(Context)
class Bonus {
  constructor(salary, strategy) {
    this.salary = salary;
    this.strategy = strategy;
  }

  getBonus() {
    return strategies[this.strategy](this.salary);
  }
}

// 客户端调用
const bonusS = new Bonus(10000, "S");
console.log(bonusS.getBonus()); // 40000

const bonusA = new Bonus(10000, "A");
console.log(bonusA.getBonus()); // 30000

模板方法模式(Template Method)

模板方法模式(Template Method Pattern)定义一个操作中的算法的骨架,而将一些步骤延迟到子类中实现。

模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。

前端中的模板方法模式场景

  • UI组件生命周期VueReact 组件的生命周期钩子(如 componentDidMountcreated)就是模板方法模式的应用。框架定义了组件渲染的整体流程,开发者在特定的钩子中实现自定义逻辑。
  • HTTP请求封装:定义一个基础的请求类,处理通用的配置(如 URL、Headers),子类实现具体的请求逻辑(如 GET、POST 参数处理)。

模板方法模式-JS实现

// 抽象父类:饮料
class Beverage {
  // 模板方法,定义算法骨架
  makeBeverage() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
  }

  boilWater() {
    console.log("煮沸水");
  }

  pourInCup() {
    console.log("倒进杯子");
  }

  // 抽象方法,子类必须实现
  brew() {
    throw new Error("抽象方法不能调用");
  }

  addCondiments() {
    throw new Error("抽象方法不能调用");
  }
}

// 具体子类:咖啡
class Coffee extends Beverage {
  brew() {
    console.log("用沸水冲泡咖啡");
  }

  addCondiments() {
    console.log("加糖和牛奶");
  }
}

// 具体子类:茶
class Tea extends Beverage {
  brew() {
    console.log("用沸水浸泡茶叶");
  }

  addCondiments() {
    console.log("加柠檬");
  }
}

// 客户端调用
const coffee = new Coffee();
coffee.makeBeverage();
// 输出:
// 煮沸水
// 用沸水冲泡咖啡
// 倒进杯子
// 加糖和牛奶

const tea = new Tea();
tea.makeBeverage();
// 输出:
// 煮沸水
// 用沸水浸泡茶叶
// 倒进杯子
// 加柠檬

观察者模式(Observer)

观察者模式(Observer Pattern)定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

前端中的观察者模式场景

  • DOM事件监听document.addEventListener 就是最典型的观察者模式。
  • Promisethen 方法也是一种观察者模式,当 Promise 状态改变时,执行相应的回调。
  • Vue响应式系统Dep(目标)和 Watcher(观察者)实现了数据的响应式更新。
  • RxJS:基于观察者模式的响应式编程库。
  • Event Bus:事件总线。

观察者模式-JS实现

// 目标对象(Subject)
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

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

  notify(data) {
    this.observers.forEach(observer => {
      observer.update(data);
    });
  }
}

// 观察者对象(Observer)
class Observer {
  constructor(name) {
    this.name = name;
  }

  update(data) {
    console.log(`${this.name} 收到通知: ${data}`);
  }
}

// 客户端调用
const subject = new Subject();
const observer1 = new Observer("观察者1");
const observer2 = new Observer("观察者2");

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notify("更新数据了!");
// 输出:
// 观察者1 收到通知: 更新数据了!
// 观察者2 收到通知: 更新数据了!

迭代器模式(Iterator)

迭代器模式(Iterator Pattern)提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

前端中的迭代器模式场景

  • 数组遍历forEachmap 等数组方法。
  • ES6 IteratorSymbol.iterator 接口,使得对象可以使用 for...of 循环遍历。
  • Generators:生成器函数可以生成迭代器。

迭代器模式-JS实现

// 自定义迭代器
class Iterator {
  constructor(items) {
    this.items = items;
    this.index = 0;
  }

  hasNext() {
    return this.index < this.items.length;
  }

  next() {
    return this.items[this.index++];
  }
}

// 客户端调用
const items = [1, 2, 3];
const iterator = new Iterator(items);

while (iterator.hasNext()) {
  console.log(iterator.next());
}
// 输出:1 2 3

// ES6 Iterator 示例
const iterableObj = {
  items: [10, 20, 30],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.items.length) {
          return { value: this.items[index++], done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
};

for (const item of iterableObj) {
  console.log(item);
}
// 输出:10 20 30

责任链模式(Chain of Responsibility)

责任链模式(Chain of Responsibility Pattern)使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

前端中的责任链模式场景

  • 事件冒泡:DOM 事件在 DOM 树中的冒泡机制就是责任链模式。
  • 中间件ExpressKoaRedux 中的中间件机制。
  • 拦截器Axios 的请求和响应拦截器。

责任链模式-JS实现

// 处理器基类
class Handler {
  setNext(handler) {
    this.nextHandler = handler;
    return handler; // 返回 handler 以支持链式调用
  }

  handleRequest(request) {
    if (this.nextHandler) {
      this.nextHandler.handleRequest(request);
    }
  }
}

// 具体处理器
class HandlerA extends Handler {
  handleRequest(request) {
    if (request === 'A') {
      console.log("HandlerA 处理了请求");
    } else {
      super.handleRequest(request);
    }
  }
}

class HandlerB extends Handler {
  handleRequest(request) {
    if (request === 'B') {
      console.log("HandlerB 处理了请求");
    } else {
      super.handleRequest(request);
    }
  }
}

class HandlerC extends Handler {
  handleRequest(request) {
    if (request === 'C') {
      console.log("HandlerC 处理了请求");
    } else {
      console.log("没有处理器处理该请求");
    }
  }
}

// 客户端调用
const handlerA = new HandlerA();
const handlerB = new HandlerB();
const handlerC = new HandlerC();

handlerA.setNext(handlerB).setNext(handlerC);

handlerA.handleRequest('A'); // HandlerA 处理了请求
handlerA.handleRequest('B'); // HandlerB 处理了请求
handlerA.handleRequest('C'); // HandlerC 处理了请求
handlerA.handleRequest('D'); // 没有处理器处理该请求

命令模式(Command)

命令模式(Command Pattern)将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

前端中的命令模式场景

  • 富文本编辑器:执行加粗、斜体、下划线等操作,并支持撤销(Undo)和重做(Redo)。
  • 菜单和按钮:将菜单项或按钮的操作封装成命令对象。

命令模式-JS实现

// 接收者:执行实际命令
class Receiver {
  execute() {
    console.log("执行命令");
  }
}

// 命令对象
class Command {
  constructor(receiver) {
    this.receiver = receiver;
  }

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

// 调用者:发起命令
class Invoker {
  constructor(command) {
    this.command = command;
  }

  invoke() {
    console.log("调用者发起请求");
    this.command.execute();
  }
}

// 客户端调用
const receiver = new Receiver();
const command = new Command(receiver);
const invoker = new Invoker(command);

invoker.invoke();
// 输出:
// 调用者发起请求
// 执行命令

备忘录模式(Memento)

备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

前端中的备忘录模式场景

  • 状态管理Redux 等状态管理库的时间旅行(Time Travel)功能。
  • 表单草稿:保存用户输入的表单内容,以便下次恢复。
  • 撤销/重做:编辑器中的撤销和重做功能。

备忘录模式-JS实现

// 备忘录:保存状态
class Memento {
  constructor(state) {
    this.state = state;
  }

  getState() {
    return this.state;
  }
}

// 发起人:需要保存状态的对象
class Originator {
  constructor() {
    this.state = "";
  }

  setState(state) {
    this.state = state;
    console.log(`当前状态: ${this.state}`);
  }

  saveStateToMemento() {
    return new Memento(this.state);
  }

  getStateFromMemento(memento) {
    this.state = memento.getState();
    console.log(`恢复状态: ${this.state}`);
  }
}

// 管理者:管理备忘录
class Caretaker {
  constructor() {
    this.mementos = [];
  }

  add(memento) {
    this.mementos.push(memento);
  }

  get(index) {
    return this.mementos[index];
  }
}

// 客户端调用
const originator = new Originator();
const caretaker = new Caretaker();

originator.setState("状态1");
originator.setState("状态2");
caretaker.add(originator.saveStateToMemento()); // 保存状态2

originator.setState("状态3");
caretaker.add(originator.saveStateToMemento()); // 保存状态3

originator.setState("状态4");

originator.getStateFromMemento(caretaker.get(0)); // 恢复到状态2
originator.getStateFromMemento(caretaker.get(1)); // 恢复到状态3

状态模式(State)

状态模式(State Pattern)允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

前端中的状态模式场景

  • 有限状态机(FSM):例如,Promise 的状态(Pending, Fulfilled, Rejected)。
  • 组件状态管理:例如,一个按钮可能有 loadingdisableddefault 等状态,不同状态下点击行为不同。
  • 游戏开发:角色的不同状态(如站立、奔跑、跳跃、攻击)。

状态模式-JS实现

// 状态接口
class State {
  handle(context) {
    throw new Error("抽象方法不能调用");
  }
}

// 具体状态A
class ConcreteStateA extends State {
  handle(context) {
    console.log("当前是状态A");
    context.setState(new ConcreteStateB());
  }
}

// 具体状态B
class ConcreteStateB extends State {
  handle(context) {
    console.log("当前是状态B");
    context.setState(new ConcreteStateA());
  }
}

// 上下文
class Context {
  constructor() {
    this.state = new ConcreteStateA();
  }

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

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

// 客户端调用
const context = new Context();
context.request(); // 当前是状态A
context.request(); // 当前是状态B
context.request(); // 当前是状态A

访问者模式(Visitor)

访问者模式(Visitor Pattern)表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

前端中的访问者模式场景

  • AST遍历Babel 插件开发中,通过访问者模式遍历和修改 AST(抽象语法树)节点。
  • 复杂数据结构处理:对树形结构或图形结构进行不同的操作(如渲染、序列化、验证)。

访问者模式-JS实现

// 元素类
class Element {
  accept(visitor) {
    throw new Error("抽象方法不能调用");
  }
}

class ConcreteElementA extends Element {
  accept(visitor) {
    visitor.visitConcreteElementA(this);
  }

  operationA() {
    return "ElementA";
  }
}

class ConcreteElementB extends Element {
  accept(visitor) {
    visitor.visitConcreteElementB(this);
  }

  operationB() {
    return "ElementB";
  }
}

// 访问者类
class Visitor {
  visitConcreteElementA(element) {
    console.log(`访问者访问 ${element.operationA()}`);
  }

  visitConcreteElementB(element) {
    console.log(`访问者访问 ${element.operationB()}`);
  }
}

// 客户端调用
const elementA = new ConcreteElementA();
const elementB = new ConcreteElementB();
const visitor = new Visitor();

elementA.accept(visitor); // 访问者访问 ElementA
elementB.accept(visitor); // 访问者访问 ElementB

中介者模式(Mediator)

中介者模式(Mediator Pattern)用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

前端中的中介者模式场景

  • MVC/MVVM框架:Controller 或 ViewModel 充当中介者,协调 View 和 Model 之间的交互。
  • 复杂表单交互:例如,选择省份后,城市下拉框需要更新;选择城市后,区县下拉框需要更新。使用中介者统一管理这些联动逻辑。
  • 聊天室:用户之间不直接发送消息,而是通过服务器(中介者)转发。

中介者模式-JS实现

// 中介者
class ChatMediator {
  constructor() {
    this.users = [];
  }

  addUser(user) {
    this.users.push(user);
    user.setMediator(this);
  }

  sendMessage(message, user) {
    this.users.forEach(u => {
      if (u !== user) {
        u.receive(message);
      }
    });
  }
}

// 用户类
class User {
  constructor(name) {
    this.name = name;
    this.mediator = null;
  }

  setMediator(mediator) {
    this.mediator = mediator;
  }

  send(message) {
    console.log(`${this.name} 发送消息: ${message}`);
    this.mediator.sendMessage(message, this);
  }

  receive(message) {
    console.log(`${this.name} 收到消息: ${message}`);
  }
}

// 客户端调用
const mediator = new ChatMediator();
const user1 = new User("User1");
const user2 = new User("User2");
const user3 = new User("User3");

mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);

user1.send("大家好!");
// 输出:
// User1 发送消息: 大家好!
// User2 收到消息: 大家好!
// User3 收到消息: 大家好!

解释器模式(Interpreter)

解释器模式(Interpreter Pattern)给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器用来解释语言中的句子。

前端中的解释器模式场景

  • 模板引擎MustacheHandlebars 等模板引擎,解析模板字符串并生成 HTML。
  • 编译器前端:将代码解析为 AST。
  • 数学表达式计算:解析并计算字符串形式的数学表达式。

解释器模式-JS实现

// 抽象表达式
class Expression {
  interpret(context) {
    throw new Error("抽象方法不能调用");
  }
}

// 终结符表达式:数字
class NumberExpression extends Expression {
  constructor(number) {
    super();
    this.number = number;
  }

  interpret(context) {
    return this.number;
  }
}

// 非终结符表达式:加法
class AddExpression extends Expression {
  constructor(left, right) {
    super();
    this.left = left;
    this.right = right;
  }

  interpret(context) {
    return this.left.interpret(context) + this.right.interpret(context);
  }
}

// 非终结符表达式:减法
class SubtractExpression extends Expression {
  constructor(left, right) {
    super();
    this.left = left;
    this.right = right;
  }

  interpret(context) {
    return this.left.interpret(context) - this.right.interpret(context);
  }
}

// 客户端调用:计算 10 + 5 - 2
const expression = new SubtractExpression(
  new AddExpression(new NumberExpression(10), new NumberExpression(5)),
  new NumberExpression(2)
);

console.log(expression.interpret()); // 13

项目地址

设计模式-结构型

设计模式-结构型

本文主要介绍下结构型设计模式,包括适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式,提供前端场景和 ES6 代码的实现过程。 供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

本文主要介绍下结构型设计模式,包括适配器模式装饰器模式代理模式外观模式桥接模式组合模式享元模式,提供前端场景和 ES6 代码的实现过程。

什么是结构型

结构型模式(Structural Patterns)主要关注对象组合。这些模式描述了如何将类或对象结合在一起形成更大的结构,同时保持结构的灵活高效。结构型模式通过继承组合的方式来简化系统的设计,解决对象之间的耦合问题,使得系统更加容易扩展和维护。

适配器模式(Adapter)

适配器模式(Adapter Pattern)将一个类的接口转换成客户希望的另一个接口,使原本因接口不兼容而不能一起工作的类可以一起工作。

适配器模式通常用于包装现有的类,以便与新的接口或系统进行交互。

前端中的适配器模式场景

  • 接口数据适配:后端返回的数据结构可能与前端组件需要的数据结构不一致,可以使用适配器模式进行转换。
  • 旧接口兼容:在系统重构或升级时,保持对旧接口的兼容性,使用适配器模式将新接口映射到旧接口。
  • Vue计算属性:Vue 中的 computed 属性可以看作是一种适配器,将原始数据转换为视图需要的数据格式。

适配器模式-JS实现

// 旧接口
class OldCalculator {
  constructor() {
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return term1 + term2;
        case 'sub':
          return term1 - term2;
        default:
          return NaN;
      }
    };
  }
}

// 新接口
class NewCalculator {
  add(term1, term2) {
    return term1 + term2;
  }

  sub(term1, term2) {
    return term1 - term2;
  }
}

// 适配器类
class CalculatorAdapter {
  constructor() {
    this.calculator = new NewCalculator();
  }

  operations(term1, term2, operation) {
    switch (operation) {
      case 'add':
        return this.calculator.add(term1, term2);
      case 'sub':
        return this.calculator.sub(term1, term2);
      default:
        return NaN;
    }
  }
}

// 客户端调用
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15

const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15

const adapter = new CalculatorAdapter();
console.log(adapter.operations(10, 5, 'add')); // 15

装饰器模式(Decorator)

装饰器模式(Decorator Pattern动态地给一个对象添加一些额外的职责,而不影响该对象所属类的其他实例。

装饰器模式提供了一种灵活的替代继承方案,用于扩展对象的功能。

前端中的装饰器模式场景

  • 高阶组件(HOC):在 React 中,高阶组件本质上就是装饰器模式的应用,用于复用组件逻辑。
  • 类装饰器:在 ES7 装饰器语法或 TypeScript 中,可以使用装饰器来增强类或类的方法,例如用于日志记录、性能监控、权限控制等。

装饰器模式-JS实现

// 原始对象
class Circle {
  draw() {
    console.log("画一个圆形");
  }
}

// 装饰器基类
class Decorator {
  constructor(circle) {
    this.circle = circle;
  }

  draw() {
    this.circle.draw();
  }
}

// 具体装饰器:添加红色边框
class RedBorderDecorator extends Decorator {
  draw() {
    this.circle.draw();
    this.setRedBorder();
  }

  setRedBorder() {
    console.log("添加红色边框");
  }
}

// 客户端调用
const circle = new Circle();
circle.draw();
// 输出:
// 画一个圆形

const redCircle = new RedBorderDecorator(new Circle());
redCircle.draw();
// 输出:
// 画一个圆形
// 添加红色边框

代理模式(Proxy)

代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。

代理模式可以在访问对象之前或之后执行额外的操作,如权限验证、延迟加载、缓存等。

前端中的代理模式场景

  • 数据响应式Vue 3 使用 Proxy 对象来实现数据的响应式系统,拦截对象的读取和修改操作。
  • 网络请求代理:在开发环境中,配置代理服务器(如 webpack-dev-server 的 proxy)解决跨域问题。
  • 虚拟代理:例如图片懒加载,先显示占位图,等图片加载完成后再替换为真实图片。
  • 缓存代理:对于开销较大的计算结果或网络请求结果进行缓存,下次请求时直接返回缓存结果。

代理模式-JS实现

// 真实图片加载类
class RealImage {
  constructor(fileName) {
    this.fileName = fileName;
    this.loadFromDisk(fileName);
  }

  loadFromDisk(fileName) {
    console.log("正在从磁盘加载 " + fileName);
  }

  display() {
    console.log("显示 " + this.fileName);
  }
}

// 代理图片类
class ProxyImage {
  constructor(fileName) {
    this.fileName = fileName;
  }

  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.fileName);
    }
    this.realImage.display();
  }
}

// 客户端调用
const image = new ProxyImage("test.jpg");

// 第一次调用,加载图片
image.display();
// 输出:
// 正在从磁盘加载 test.jpg
// 显示 test.jpg

// 第二次调用,直接显示
image.display();
// 输出:
// 显示 test.jpg

外观模式(Facade)

外观模式(Facade Pattern)提供一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。

前端中的外观模式场景

  • 浏览器兼容性封装:封装不同浏览器的 API 差异,提供统一的接口。例如,封装事件监听函数,统一处理 addEventListenerattachEvent
  • 简化复杂库的使用:例如 jQueryAxios,它们为复杂的原生 DOM 操作或 XMLHttpRequest 提供了简单易用的接口。

外观模式-JS实现

// 子系统1:灯光
class Light {
  on() {
    console.log("开灯");
  }
  off() {
    console.log("关灯");
  }
}

// 子系统2:电视
class TV {
  on() {
    console.log("打开电视");
  }
  off() {
    console.log("关闭电视");
  }
}

// 子系统3:音响
class SoundSystem {
  on() {
    console.log("打开音响");
  }
  off() {
    console.log("关闭音响");
  }
}

// 外观类:家庭影院
class HomeTheaterFacade {
  constructor(light, tv, sound) {
    this.light = light;
    this.tv = tv;
    this.sound = sound;
  }

  watchMovie() {
    console.log("--- 准备看电影 ---");
    this.light.off();
    this.tv.on();
    this.sound.on();
  }

  endMovie() {
    console.log("--- 结束看电影 ---");
    this.light.on();
    this.tv.off();
    this.sound.off();
  }
}

// 客户端调用
const light = new Light();
const tv = new TV();
const sound = new SoundSystem();
const homeTheater = new HomeTheaterFacade(light, tv, sound);

homeTheater.watchMovie();
// 输出:
// --- 准备看电影 ---
// 关灯
// 打开电视
// 打开音响

homeTheater.endMovie();
// 输出:
// --- 结束看电影 ---
// 开灯
// 关闭电视
// 关闭音响

桥接模式(Bridge)

桥接模式(Bridge Pattern)将抽象部分与它的实现部分分离,使它们可以独立地变化。

前端中的桥接模式场景

  • UI组件与渲染引擎分离:例如,一个通用的图表库,可以将图表的逻辑(抽象部分)与具体的渲染方式(实现部分,如 Canvas、SVG、WebGL)分离。
  • 事件监听:在绑定事件时,将回调函数(实现部分)与事件绑定(抽象部分)分离,使得回调函数可以复用。

桥接模式-JS实现

// 实现部分接口:颜色
class Color {
  fill() {
    throw new Error("抽象方法不能调用");
  }
}

class Red extends Color {
  fill() {
    return "红色";
  }
}

class Green extends Color {
  fill() {
    return "绿色";
  }
}

// 抽象部分:形状
class Shape {
  constructor(color) {
    this.color = color;
  }

  draw() {
    throw new Error("抽象方法不能调用");
  }
}

class Circle extends Shape {
  draw() {
    console.log(`画一个${this.color.fill()}的圆形`);
  }
}

class Square extends Shape {
  draw() {
    console.log(`画一个${this.color.fill()}的正方形`);
  }
}

// 客户端调用
const redCircle = new Circle(new Red());
redCircle.draw(); // 画一个红色的圆形

const greenSquare = new Square(new Green());
greenSquare.draw(); // 画一个绿色的正方形

组合模式(Composite)

组合模式(Composite Pattern)将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

前端中的组合模式场景

  • DOM树:DOM 树本身就是一个典型的组合模式结构,既包含具体的节点(如 divspan),也包含包含其他节点的容器。
  • 虚拟DOMVirtual DOM 也是树形结构,组件可以包含其他组件或原生元素。
  • 文件目录系统:文件夹可以包含文件或子文件夹。
  • 级联菜单:多级菜单的展示和操作。

组合模式-JS实现

// 组件基类
class Component {
  constructor(name) {
    this.name = name;
  }

  add(component) {
    throw new Error("不支持该操作");
  }

  remove(component) {
    throw new Error("不支持该操作");
  }

  print(indent = "") {
    throw new Error("不支持该操作");
  }
}

// 叶子节点:文件
class File extends Component {
  print(indent = "") {
    console.log(`${indent}- ${this.name}`);
  }
}

// 组合节点:文件夹
class Folder extends Component {
  constructor(name) {
    super(name);
    this.children = [];
  }

  add(component) {
    this.children.push(component);
  }

  remove(component) {
    const index = this.children.indexOf(component);
    if (index > -1) {
      this.children.splice(index, 1);
    }
  }

  print(indent = "") {
    console.log(`${indent}+ ${this.name}`);
    this.children.forEach(child => {
      child.print(indent + "  ");
    });
  }
}

// 客户端调用
const root = new Folder("根目录");
const folder1 = new Folder("文档");
const folder2 = new Folder("图片");

const file1 = new File("简历.doc");
const file2 = new File("照片.jpg");
const file3 = new File("logo.png");

root.add(folder1);
root.add(folder2);
folder1.add(file1);
folder2.add(file2);
folder2.add(file3);

root.print();
// 输出:
// + 根目录
//   + 文档
//     - 简历.doc
//   + 图片
//     - 照片.jpg
//     - logo.png

享元模式(Flyweight)

享元模式(Flyweight Pattern)通过共享来高效地支持大量细粒度的对象。

前端中的享元模式场景

  • 事件委托:在父元素上绑定事件监听器,通过事件冒泡处理子元素的事件,避免为每个子元素绑定监听器,节省内存。
  • 对象池:在游戏开发或复杂动画中,预先创建一组对象放入池中,使用时取出,用完归还,避免频繁创建和销毁对象。
  • DOM复用:在长列表滚动(虚拟滚动)中,只渲染可视区域的 DOM 节点,回收并复用移出可视区域的节点。

享元模式-JS实现

// 享元工厂
class ShapeFactory {
  constructor() {
    this.circleMap = {};
  }

  getCircle(color) {
    if (!this.circleMap[color]) {
      this.circleMap[color] = new Circle(color);
      console.log(`创建新的 ${color} 圆形`);
    }
    return this.circleMap[color];
  }
}

// 具体享元类
class Circle {
  constructor(color) {
    this.color = color;
  }

  draw(x, y) {
    console.log(`在 (${x}, ${y}) 画一个 ${this.color} 的圆形`);
  }
}

// 客户端调用
const factory = new ShapeFactory();

const redCircle1 = factory.getCircle("红色");
redCircle1.draw(10, 10);

const redCircle2 = factory.getCircle("红色");
redCircle2.draw(20, 20);

const blueCircle = factory.getCircle("蓝色");
blueCircle.draw(30, 30);

console.log(redCircle1 === redCircle2); // true
// 输出:
// 创建新的 红色 圆形
// 在 (10, 10) 画一个 红色 的圆形
// 在 (20, 20) 画一个 红色 的圆形
// 创建新的 蓝色 圆形
// 在 (30, 30) 画一个 蓝色 的圆形
// true

项目地址

❌