这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
Behavioral Design Patterns 行为型设计模式
关于行为型设计模式
它涉及对象之间的职责分配。 它与结构型设计模式不同的是:它们不仅描述结构,而且还概述了它们之间消息传递/通信的方法。 或者换句话说,它们有助于回答“如何在软件构件化中运行行为?”
行为型设计模式的分类
- Chain of Responsibility 职责链模式
- Command 命令模式
- Iterator 迭代器模式
- Mediator 中介者模式
- Memento 备忘录模式
- Observer 观察者模式
- Visitor 访问者模式
- Strategy 策略模式
- State 状态模式
- Template Method 模板方法模式
职责链模式
真实的案例
想象一下,您的账户中设置了三种支付方式(A、B、C),每种支付方式里面都有不同的余额。有点类似于余额支付、银行卡支付、花呗支付。付款偏好可以选择为A(余额支付),其次是B(银行卡支付),其实是C(花呗支付),其中A中有100元,B中有200元,C中有300元,然后你尝试去购买210元的物品,使用职责链模式,首先它会先去检查A是否有充足的余额进行支付,如果有,那么进行购买,否则将前进到B中,检查B的余额是否满足支付条件,如果满足,进行支付,否则继续查看C中是否有充足的余额进行支付,如果有,进行购买,否则将继续转发,知道找到合适的处理程序。这里A、B、C就是职责链的一环,整个环构成了一个职责链
简而言之
它有助于构建对象链。 请求从一端进入并不断从一个对象到另一个对象,直到找到合适的处理程序
维基百科
In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle the rest are passed to the next processing object in the chain.
在面向对象的设计中,责任链模式是一种由命令对象源和一系列处理对象组成的设计模式。 每个处理对象都包含定义它可以处理的命令对象类型的逻辑,其余的将传递给链中的下一个处理对象
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了
编程案例
以我们上面的账户为例,首先,我们得有一个基本账户,然后账户中有支付的方法和其他一些逻辑
class Account {
setNext(account) {
this.successor = account;
}
pay(amountToPay) {
if (this.canPay(amountToPay)) {
console.log(`Paid ${amountToPay} using ${this.name}`);
} else if (this.successor) {
console.log(`Cannot pay using ${this.name}. Proceeding...`);
this.successor.pay(amountToPay);
} else {
console.log('None of the accounts have enough balance');
}
}
canPay(amount) {
return this.balance >= amount;
}
}
class Balance extends Account {
constructor(balance) {
super();
this.name = 'Balance';
this.balance = balance;
}
}
class Bank extends Account {
constructor(balance) {
super();
this.name = 'Bank';
this.balance = balance;
}
}
class Huabei extends Account {
constructor(balance) {
super();
this.name = 'Huabei';
this.balance = balance;
}
}
现在让我们使用上面定义的类(即余额、银行、花呗)来准备连接我们的职责链
const balance = new Balance(100); // Balance with balance 100
const bank = new Bank(200); // Bank with balance 200
const huabei = new Huabei(300); // Huabei with balance 300
balance.setNext(bank);
bank.setNext(huabei);
现在让我们尝试进行支付
balance.pay(201);
// Cannot pay using Balance. Proceeding ..
// Cannot pay using Bank. Proceeding ..:
// Paid 201 using Huabei!
什么时候使用
在处理消息的时候用来过滤很多道;简化了对象,使得对象不需要知道链的结构;增加新的请求处理类很方便;但是不能保证请求一定被接收,而且系统性能将受到一定影响。当有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定,在不明确指定接收者的情况下,向多个对象中的一个提交一个请求,可动态指定一组对象处理请求
命令模式
真实的案例
很经典的餐厅点餐,你框框的点了一堆菜,然后把服务员叫过来,准备下单,这是你是客户,服务员就是调用者,你要求服务员给你带来一些炒菜(命令),服务员只需要将你的需求转发给了解炒菜技能和方法的厨师(接收者)
简而言之
允许您将操作封装在对象中。 这种模式背后的关键思想是提供一种将客户端与接收器解耦的方法
维基百科
In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
在面向对象编程中,命令模式是一种行为设计模式,其中对象用于封装执行操作或稍后触发事件所需的所有信息。 此信息包括方法名称、拥有该方法的对象和方法参数的值
通过调用者调用接受者执行命令,顺序:调用者→命令→接受者,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,无法抵御变化的紧耦合的设计就不太合适
编程案例
首先,我们有接收器,它具有可以执行的每个动作的实现
class Bulb {
turnOn() {
console.log('Bulb has been lit');
}
turnOff() {
console.log('Darkness!');
}
}
然后我们有一个接口,每个命令将要实现,然后我们有一组命令
class TurnOnCommand {
constructor(bulb) {
this.bulb = bulb;
}
execute() {
this.bulb.turnOn();
}
undo() {
this.bulb.turnOff();
}
redo() {
this.execute();
}
}
class TurnOffCommand {
constructor(bulb) {
this.bulb = bulb;
}
execute() {
this.bulb.turnOff();
}
undo() {
this.bulb.turnOn();
}
redo() {
this.execute();
}
}
然后我们有一个调用者,客户端将与它交互以处理一些命令
class RemoteControl {
submit(command) {
command.execute();
}
}
然后我们来看看怎么在客户端中使用它
const bulb = new Bulb();
const turnOn = new TurnOnCommand(bulb);
const turnOff = new TurnOffCommand(bulb);
const remote = new RemoteControl();
remote.submit(turnOn); // Bulb has been lit!
remote.submit(turnOff); // Darkness!
命令模式也可用于实现基于事务的系统。 您在执行命令后立即维护命令的历史记录。 如果最终命令成功执行,则一切正常,否则只需遍历历史记录并继续对所有执行的命令执行撤消
什么时候使用
认为是命令的地方都可以使用命令模式,系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式
迭代器模式
真实的案例
想象一下,一个旧的收音机将会是迭代器很好的一个例子,用户可以从某个频道开始,使用上一个或下一个按钮来浏览各个频道,或者MP3、电视机等,你可以按下上下键来切换,它们都提供了一个界面让你操作,可以用来遍历所有的频道
简而言之
它提供了一种在不暴露底层实现的情况下访问对象元素的方法
维基百科
In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.
在面向对象编程中,迭代器模式是一种设计模式,其中迭代器用于遍历容器并访问容器的元素。 迭代器模式在某些情况下将算法与容器解耦,算法必然是特定于容器的,因此无法解耦
编程案例
以我们上面的收音机为例,首先我们有一个 RadioStation
class RadioStation {
constructor(frequency) {
this.frequency = frequency;
}
getFrequency() {
return this.frequency;
}
}
然后我们来开始实现我们的迭代器
class StationList {
constructor() {
this.stations = [];
}
addStation(station) {
this.stations.push(station);
}
removeStation(toRemove) {
const toRemoveFrequency = toRemove.getFrequency();
this.stations = this.stations.filter(station => {
return station.getFrequency() !== toRemoveFrequency;
});
}
}
然后我们可以这样使用
const stationList = new StationList();
stationList.addStation(new RadioStation(89));
stationList.addStation(new RadioStation(101));
stationList.addStation(new RadioStation(102));
stationList.addStation(new RadioStation(103.2));
stationList.stations.forEach(station => console.log(station.getFrequency()));
// 89
// 101
// 102
// 103.2
stationList.removeStation(new RadioStation(89)); // 将会移除 89 频道
什么时候使用
访问一个聚合对象的内容而无须暴露它的内部表示,需要为聚合对象提供多种遍历方式,为遍历不同的聚合结构提供一个统一的接口都可以使用迭代器模式
中介者模式
真实的案例
想象一下,当您通过手机与某人通话时,你和他之间有一个网络提供商,您的对话会通过它而不是直接发送。 在这种情况下,网络提供者是中介者;再者中国加入 WTO 之前都是各个国家项目贸易,结构复杂,加入后通过 WTO 进行互相贸易;再者机场的调度系统;我们常说的 MVC 框架,其中 C 就是中介者
简而言之
中介者模式添加了一个第三方对象(称为中介者)来控制两个对象(称为同事)之间的交互。 它有助于减少相互通信的类之间的耦合。 因为现在他们不需要知道彼此的实现
维基百科
In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior.
在软件工程中,中介者模式定义了一个对象,该对象封装了一组对象如何交互。 这种模式被认为是一种行为模式,因为它可以改变程序的运行行为
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
编程案例
这是聊天室(即中介者)的最简单示例,用户(即同事)相互发送消息
首先我们有一个中介者,即聊天室
class ChatRoom {
showMessage(user, message) {
const time = new Date();
const sender = user.getName();
console.log(time + '[' + sender + ']:' + message);
}
}
然后我们有我们的用户,比如说同事
class User {
constructor(name, chatMediator) {
this.name = name;
this.chatMediator = chatMediator;
}
getName() {
return this.name;
}
send(message) {
this.chatMediator.showMessage(this, message);
}
}
然后我们可以这样使用
const mediator = new ChatRoom();
const john = new User('John Doe', mediator);
const jane = new User('Jane Doe', mediator);
john.send('Hi there!');
jane.send('Hey!');
// Mon Aug 23 2021 18:01:08 GMT+0800 (中国标准时间)[John Doe]:Hi there!
// Mon Aug 23 2021 18:01:08 GMT+0800 (中国标准时间)[Jane Doe]:Hey!
什么时候使用
系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象;想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类
备忘录模式
真实的案例
想象一下,以计算器(即发起者)为例,每当您执行某些计算时,最后一次计算都会保存在内存中(即备忘录),以便您可以返回并使用某些操作按钮(即看管人)恢复它
简而言之
备忘录模式是关于以一种可以在以后以平滑恢复的方式捕获和存储对象的当前状态
维基百科
The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).
备忘录模式是一种软件设计模式,它提供了将对象恢复到其先前状态(通过回滚撤消)的能力
当您需要提供某种撤消功能时,通常很有用
编程案例
让我们举一个文本编辑器的例子,它不时地保存状态,你可以根据需要恢复
首先,我们有我们的 memento 对象,它将能够保持编辑器状态
class EditorMemento {
constructor(content) {
this._content = content;
}
getContent() {
return this._content;
}
}
然后我们有我们的编辑器,即将使用 memento 创建出来的对象
class Editor {
constructor() {
this._content = '';
}
type(words) {
this._content = this._content + ' ' + words;
}
getContent() {
return this._content;
}
save() {
return new EditorMemento(this._content);
}
restore(memento) {
this._content = memento.getContent();
}
}
然后我们可以这样使用
const editor = new Editor();
// 输入一些东西
editor.type('This is the first sentence.');
editor.type('This is second.');
// 保存状态: This is the first sentence. This is second.
const saved = editor.save();
// 再次输入一些东西
editor.type('And this is third.');
// 在保存之前我们看下输出
console.log(editor.getContent()); // This is the first sentence. This is second. And this is third.
// 恢复到上次保存的节点
editor.restore(saved);
console.log(editor.getContent()); // This is the first sentence. This is second.
什么时候使用
需要保存/恢复数据的相关状态场景;提供一个可回滚的操作;可以应用在打游戏的存档,浏览器的后退、电脑操作上的撤销、数据库的事务管理等
观察者模式(也叫发布-订阅者模式)
真实的案例
一个很好的例子是求职者,他们订阅了一些招聘网站,只要有匹配的工作机会,他们就会收到通知
简而言之
定义对象之间的依赖关系,以便每当对象更改其状态时,都会通知其所有依赖项
维基百科
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
观察者模式是一种软件设计模式,其中称为主体的对象维护一个称为观察者的依赖项列表,并通常通过调用它们的方法之一来自动通知它们任何状态更改
编程案例
以上面的求职招聘的例子为例。 首先,我们有求职者需要收到招聘通知
const JobPost = title => ({
title: title
});
class JobSeeker {
constructor(name) {
this._name = name;
}
notify(jobPost) {
console.log(this._name, 'has been notified of a new posting :', jobPost.title);
}
}
然后我们有求职者会订阅一些招聘信息
class JobBoard {
constructor() {
this._subscribers = [];
}
subscribe(jobSeeker) {
this._subscribers.push(jobSeeker);
}
addJob(jobPosting) {
this._subscribers.forEach(subscriber => {
subscriber.notify(jobPosting);
});
}
}
然后我们可以这样使用
const jonDoe = new JobSeeker('John Doe');
const janeDoe = new JobSeeker('Jane Doe');
const kaneDoe = new JobSeeker('Kane Doe');
const jobBoard = new JobBoard();
jobBoard.subscribe(jonDoe);
jobBoard.subscribe(janeDoe);
jobBoard.addJob(JobPost('Software Engineer'));
// John Doe has been notified of a new posting : Software Engineer
// Jane Doe has been notified of a new posting : Software Engineer
什么时候使用
一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知;一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用;一个对象必须通知其他对象,而并不知道这些对象是谁;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度
访问者模式
真实的案例
您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式;
简而言之
访问者模式让您无需修改对象即可向对象添加更多操作
维基百科
In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.
在面向对象的编程和软件工程中,访问者设计模式是一种将算法与其操作的对象结构分离的方法。 这种分离的实际结果是能够在不修改现有对象结构的情况下向现有对象结构添加新操作。 这是遵循开放/封闭原则的一种方式
编程案例
让我们举一个动物园的例子,我们有几种不同的动物,我们来让它们发出声音。 让我们使用访问者模式来实现这个,我们有动物的类的实现
class Monkey {
shout() {
console.log('Ooh oo aa aa!');
}
accept(operation) {
operation.visitMonkey(this);
}
}
class Lion {
roar() {
console.log('Roaaar!');
}
accept(operation) {
operation.visitLion(this);
}
}
class Dolphin {
speak() {
console.log('Tuut tuttu tuutt!');
}
accept(operation) {
operation.visitDolphin(this);
}
}
让我们实现我们的 visitor
const speak = {
visitMonkey(monkey) {
monkey.shout();
},
visitLion(lion) {
lion.roar();
},
visitDolphin(dolphin) {
dolphin.speak();
}
};
然后我们可以这样使用
const monkey = new Monkey();
const lion = new Lion();
const dolphin = new Dolphin();
monkey.accept(speak); // Ooh oo aa aa!
lion.accept(speak); // Roaaar!
dolphin.accept(speak); // Tuut tutt tuutt!
我们可以简单地通过为动物设置继承层次结构来实现这一点,但是每当我们必须向动物添加新动作时,我们就必须修改动物。 但是现在我们不必更改它们。 例如,假设我们被要求向动物添加跳跃行为,我们可以简单地通过创建一个新访问者来添加它,即
const jump = {
visitMonkey(monkey) {
console.log('Jumped 20 feet high! on to the tree!');
},
visitLion(lion) {
console.log('Jumped 7 feet! Back on the ground!');
},
visitDolphin(dolphin) {
console.log('Walked on water a little and disappeared');
}
};
然后
monkey.accept(speak); // Ooh oo aa aa!
monkey.accept(jump); // Jumped 20 feet high! on to the tree!
lion.accept(speak); // Roaaar!
lion.accept(jump); // Jumped 7 feet! Back on the ground!
dolphin.accept(speak); // Tuut tutt tuutt!
dolphin.accept(jump); // Walked on water a little and disappeared
什么时候使用
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中
策略模式
真实的案例
考虑一个排序的例子,我们实现了冒泡排序,但数据开始增长,冒泡排序开始变得非常缓慢。 为了解决这个问题,我们实现了快速排序。 但是现在,尽管快速排序算法对于大型数据集表现更好,但对于较小的数据集却非常缓慢。 为了解决这个问题,我们实施了一种策略,对于小数据集,将使用冒泡排序,而对于更大的采用快速排序
简而言之
策略模式允许您根据情况切换算法或策略
维基百科
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioural software design pattern that enables an algorithm's behavior to be selected at runtime.
在计算机编程中,策略模式(也称为策略模式)是一种行为软件设计模式,可以在运行时选择算法的行为
编程案例
以上面的为例
const bubbleSort = dataset => {
console.log('Sorting with bubble sort');
// ...
// ...
return dataset;
};
const quickSort = dataset => {
console.log('Sorting with quick sort');
// ...
// ...
return dataset;
};
然后我们将使用策略模式
const sorter = dataset => {
if (dataset.length > 5) {
return quickSort;
} else {
return bubbleSort;
}
};
然后我们可以这样使用
const longDataSet = [ 1, 5, 4, 3, 2, 8 ];
const shortDataSet = [ 1, 5, 4 ];
const sorter1 = sorter(longDataSet);
const sorter2 = sorter(shortDataSet);
sorter1(longDataSet); // Sorting with quick sort
sorter2(shortDataSet); // Sorting with bubble sort
什么时候使用
个系统有许多许多类,而区分它们的只是他们直接的行为
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为
一个系统需要动态地在几种算法中选择一种
如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现
状态模式
真实的案例
想象一下你正在使用一些绘图应用程序,你选择画笔来绘制。 现在画笔会根据所选颜色更改其行为,即如果您选择了红色,它将以红色绘制,如果是蓝色,则它将以蓝色绘制等等
简而言之
它允许您在状态更改时更改类的行为
维基百科
The state pattern is a behavioral software design pattern that implements a state machine in an object-oriented way. With the state pattern, a state machine is implemented by implementing each individual state as a derived class of the state pattern interface, and implementing state transitions by invoking methods defined by the pattern's superclass. The state pattern can be interpreted as a strategy pattern which is able to switch the current strategy through invocations of methods defined in the pattern's interface
状态模式是一种以面向对象的方式实现状态机的行为软件设计模式。 使用状态模式,通过将每个单独的状态实现为状态模式接口的派生类,并通过调用由模式的超类定义的方法来实现状态转换来实现状态机。 状态模式可以解释为一种策略模式,它能够通过调用模式接口中定义的方法来切换当前策略
编程案例
让我们举一个文本编辑器的例子,它让你改变输入文本的状态,即如果你选择了粗体,它开始以粗体书写,如果是斜体,则以斜体等等
首先,我们有转换的函数
const upperCase = inputString => inputString.toUpperCase();
const lowerCase = inputString => inputString.toLowerCase();
const defaultTransform = inputString => inputString;
然后我们有我们的编辑器
class TextEditor {
constructor(transform) {
this._transform = transform;
}
setTransform(transform) {
this._transform = transform;
}
type(words) {
console.log(this._transform(words));
}
}
然后我们可以这样使用
const editor = new TextEditor(defaultTransform);
editor.type('First line');
editor.setTransform(upperCase);
editor.type('Second line');
editor.type('Third line');
editor.setTransform(lowerCase);
editor.type('Fourth line');
editor.type('Fifth line');
// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line
什么时候使用
代码中包含大量与对象状态有关的条件语句;行为随状态改变而改变的场景;条件、分支语句的代替者
模板方法模式
真实的案例
假设我们正在建造一个房子。 构建步骤可能看起来像下面这样
准备房子的基础;建造墙壁;添加屋顶;添加其他楼层。我的意思是指这些建造的顺序是不会改变的,所以这些步骤我们可以抽象出一个模板方法,可以应用到所有使用这种方法构建房子中
简而言之
模板方法定义了如何执行某些算法的框架,但将这些步骤的实现推迟到子类
维基百科
In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
在软件工程中,模板方法模式是一种行为设计模式,它定义了操作中算法的程序框架,将一些步骤推迟到子类中。 它让人们在不改变算法结构的情况下重新定义算法的某些步骤
编程案例
想象一下,我们有一个构建工具,可以帮助我们测试、lint、构建、生成构建报告(即代码覆盖率报告、linting 报告等)并在测试服务器上部署我们的应用程序
首先我们有自己的基类,它指定了构建算法的骨架
class Builder {
// Template method
build() {
this.test(); // 测试
this.lint(); // 校验
this.assemble(); // 构建
this.deploy(); // 布署
}
}
然后我们可以这样实现
class AndroidBuilder extends Builder {
test() {
console.log('Running android tests');
}
lint() {
console.log('Linting the android code');
}
assemble() {
console.log('Assembling the android build');
}
deploy() {
console.log('Deploying android build to server');
}
}
class IosBuilder extends Builder {
test() {
console.log('Running ios tests');
}
lint() {
console.log('Linting the ios code');
}
assemble() {
console.log('Assembling the ios build');
}
deploy() {
console.log('Deploying ios build to server');
}
}
然后我们可以这样使用
const androidBuilder = new AndroidBuilder();
androidBuilder.build();
// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server
const iosBuilder = new IosBuilder();
iosBuilder.build();
// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server
什么时候使用
有一些通用的方法;有多个子类共有的方法,且逻辑相同;重要的、复杂的方法,可以考虑作为模板方法
参考资料
- Javascript 设计模式
- 《Javascript设计模式——张容铭》