设计模式-行为型
设计模式-行为型
本文主要介绍下行为型设计模式,包括
策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式,提供前端场景和 ES6 代码的实现过程。 供自己以后查漏补缺,也欢迎同道朋友交流学习。
引言
本文主要介绍下行为型设计模式,包括策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式,提供前端场景和 ES6 代码的实现过程。
什么是行为型
行为型模式(Behavioral Patterns)主要关注对象之间的通信和职责分配。这些模式描述了对象之间如何协作共同完成任务,以及如何分配职责。行为型模式不仅关注类和对象的结构,更关注它们之间的相互作用,通过定义清晰的通信机制,解决系统中复杂的控制流问题,使得代码更加清晰、灵活和易于维护。
行为型模式
策略模式(Strategy)
策略模式(Strategy Pattern)定义一系列的算法,把它们一个个封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户而变化。
前端中的策略模式场景
- 表单验证:将不同的验证规则(如非空、邮箱格式、手机号格式)封装成策略,根据需要选择验证策略。
- 不同业务逻辑处理:例如,根据用户权限(普通用户、VIP、管理员)展示不同的 UI 或执行不同的逻辑。
-
缓动动画算法:在动画库中,提供多种缓动函数(如
linear、ease-in、ease-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组件生命周期:
Vue或React组件的生命周期钩子(如componentDidMount、created)就是模板方法模式的应用。框架定义了组件渲染的整体流程,开发者在特定的钩子中实现自定义逻辑。 - 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就是最典型的观察者模式。 -
Promise:
then方法也是一种观察者模式,当 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)提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
前端中的迭代器模式场景
-
数组遍历:
forEach、map等数组方法。 -
ES6 Iterator:
Symbol.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 树中的冒泡机制就是责任链模式。
-
中间件:
Express、Koa、Redux中的中间件机制。 -
拦截器:
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)。
-
组件状态管理:例如,一个按钮可能有
loading、disabled、default等状态,不同状态下点击行为不同。 - 游戏开发:角色的不同状态(如站立、奔跑、跳跃、攻击)。
状态模式-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)给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器用来解释语言中的句子。
前端中的解释器模式场景
-
模板引擎:
Mustache、Handlebars等模板引擎,解析模板字符串并生成 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