再谈23种设计模式(3):行为型模式(学习笔记)
23 种设计模式的分类表
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 |
代理 装饰 桥接 (对象)适配器外观 享元 组合 |
策略 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 命令 |
结构型模式VS行为型模式
-
结构型模式则关注于如何组合类和对象以形成更大的结构,以及如何简化这些结构(描述系统各要素之间的关系)。它们通过继承或组合来组织类或对象,以实现更大的功能。结构型模式帮助确保当一个部分改变时,整个系统的结构不需要做出大的改变。这些模式主要关注于组合对象以形成新的结构,以便更好地管理复杂性——其是,结构模型就是一种集合模型,节点表示系统要素,有向边表示要素之间的关系。
-
行为型模式的关注点在于对象之间的通信和职责分配(描述结构模型中对象的动态特征)。它们主要用于管理对象之间的算法、关系和职责。行为型模式涉及到类和对象如何交互以及分配职责。这些模式通常描述了对象之间的通信方式,以及如何协作完成任务。
总结一下,区别在于:
-
结构型模式关注的是对象和类的组织,即它们是如何构成更大的结构的,以及如何通过这种方式来简化系统的设计或提高系统的灵活性。
-
行为型模式关注的是对象之间的交云和协作,即它们是如何相互作用的,以及如何分配职责和算法来完成任务。
行为模型可以通过各种图形表示,如STD-状态转换图、活动图、状态机图等。
行为型模式(Behavioral Design Patterns)概述:
-
模板模式(Template pattern):定义一个算法结构,而将一些步骤延迟到子类实现。
-
解释器模式(Interpreter pattern):给定一个语言,定义它的文法的一种表示,并定义一个解释器。
-
策略模式(strategy pattern):定义一系列算法,把他们封装起来,并且使它们可以相互替换。
-
状态模式(State pattern):允许一个对象在其对象内部状态改变时改变它的行为。
-
观察者模式(observer pattern):对象间的一对多的依赖关系。
-
备忘录模式(Memento pattern):在不破坏封装的前提下,保持对象的内部状态。
-
中介者模式(Mediator pattern):用一个中介对象来封装一系列的对象交互。
-
命令模式(Command pattern):将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
-
访问者模式(visitor pattern):在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
-
责任链模式(Chain of responsibility pattern):将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
-
迭代器模式(iterator pattern):一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
模板方法模式/模板模式 (Template Method Pattern)
定义一个操作的算法骨架,允许子类重新定义某些步骤而不改变算法的结构。
在抽象类中公开定义了执行的方法,子类可以按需重写其方法,但是要以抽象类中定义的方式调用方法。总结起来就是:定义一个操作的算法结构,而将一些步骤延迟到子类中。在不改变算法结构的情况下,子类能重定义该算法的特定步骤。
无论造型如何变化,不变的有两种东西:“奶油”和“面包”。其余的材料随意搭配,就凑成了各式各样的蛋糕。
模板方法模式其实是一个比较简单的设计模式,它有如下优点:
-
封装不变的逻辑,扩展差异化的逻辑;
-
抽取公共代码,提高代码的复用性;
-
父类控制行为,子类实现细节。
模板模式结构
-
模板类(Abstract Class): 定义了一个算法的骨架,其中包含一个或多个抽象方法,这些方法由子类实现。还可以包含一些具体方法,这些方法在算法中的步骤中被使用,但它们的实现可以在子类中被重写。
-
具体子类(Concrete Class): 实现了在模板类中定义的抽象方法,完成了算法的特定步骤。
模板模式优缺点
优点
-
抽取公共代码,代码复用,减少重复代码。
-
提高代码的可维护性,因为算法结构在模板类中统一定义,易于理解和修改。
-
提供了一个稳定的算法框架,方便框架扩展。
缺点
-
模板方法模式可能会导致类的数目增加,因为每个具体子类对应一个具体实现。
-
可能会在一定程度上限制了某些子类的灵活性,因为部分算法结构在父类中已经确定。
更详细的了解,推荐阅读:
-
聊一聊模板方法模式 https://zhuanlan.zhihu.com/p/629320399
-
什么是模板方法模式? - 大橘为重的回答 - 知乎 https://www.zhihu.com/question/573793216/answer/3330820558
-
什么是模板方法模式? - Lion Long的回答 - 知乎 https://www.zhihu.com/question/573793216/answer/3255332354
典型场景:
-
算法骨架不变,但某些步骤的具体实现可能会变化: 当有一个算法需要在其框架内保持不变的结构,但某些步骤的具体实现可能会随着算法的不同变化时,可以使用模板方法模式。
-
代码复用: 当有多个类有相似的算法,并且希望通过共享相同的代码来减少重复时,可以使用模板方法模式。
-
逻辑结构清晰,固定算法流程: 当算法的执行流程在各个子类中基本相同,只有某些细节不同的情况下,模板方法模式可以避免代码重复,使得结构更清晰。 模板方法模式定义了一个清晰的算法骨架,使得整个算法的逻辑结构更加明确。这有助于开发人员理解和维护代码。
-
框架设计: 在框架设计中,通常定义了一些抽象的模板类,由子类实现具体的算法细节。这样可以提供框架的稳定结构,同时允许具体的实现在子类中灵活变化。
模板方法模式和抽象工厂模式
-
模板方法模式属于行为型模式,它定义了一个操作中的算法的骨架,并将一些步骤延迟到子类中实现。这样,算法的结构可以在不改变算法的情况下重新定义算法的某些特定步骤。模板方法模式在一个方法中定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。这样,子类可以在不改变算法结构的前提下,重新定义算法的某些特定步骤。
-
抽象工厂模式属于创建型模式,它提供了一个接口,用于创建一系列相关或相互依赖的对象,而不需要指定它们具体的类。抽象工厂允许客户端使用抽象的接口来创建一组相关的产品,而不需要关心这些产品的具体实现。这样可以在不影响客户端的情况下更换产品族或产品的具体实现。
模板方法模式与抽象工厂模式主要区别在于:
-
目的不同:
-
模板方法模式的目的是定义一个算法的骨架,并允许子类为算法的某些步骤提供具体实现。
-
抽象工厂模式的目的是创建一系列相关或依赖的对象,而不需要关心这些对象的具体类。
-
设计层次不同:
-
模板方法模式主要关注行为和算法的封装,通过继承机制来实现。
-
抽象工厂模式关注对象创建和组合的封装,通过对象组合来实现。
-
应用场景不同:
-
模板方法模式通常用于需要定义算法步骤的场景,允许子类在不改变算法结构的情况下,改变或扩展算法中的某些步骤。
-
抽象工厂模式通常用于系统需要独立于产品的创建、组合和表示时,系统配置有多个产品族,而系统只消费其中某一族的产品
-
应用领域不同:
-
模板方法模式常用在框架的核心,如Spring的JDBC Template、Hibernate的Session Factory等。
-
抽象工厂模式更多地出现在库或工具包中,比如Apache Commons Logging、SLF4j等日志记录库。
下面是模板方法模式
// 抽象类
class Cake {
// 模板方法,定义了做蛋糕的步骤
makeCake() {
this.prepareIngredients();
this.bake();
this.decorate();
}
// 以下方法由子类实现
prepareIngredients() {
throw new Error("prepareIngredients() must be overridden");
}
bake() {
console.log("Baking the cake. Be patient.");
}
decorate() {
throw new Error("decorate() must be overridden");
}
}
// 具体类 - 草莓蛋糕
class StrawberryCake extends Cake {
prepareIngredients() {
console.log("Gathering ingredients for a Strawberry Cake.");
}
decorate() {
console.log("Decorating the cake with strawberries.");
}
}
// 具体类 - 芒果蛋糕
class MangoCake extends Cake {
prepareIngredients() {
console.log("Gathering ingredients for a Mango Cake.");
}
decorate() {
console.log("Decorating the cake with mango slices.");
}
}
// 使用模板方法
const strawberryCake = new StrawberryCake();
strawberryCake.makeCake();
const mangoCake = new MangoCake();
mangoCake.makeCake();
接下来,我们来实现抽象工厂模式的例子。我们将创建一个抽象工厂 CakeFactory,它定义了创建蛋糕和装饰品的接口。然后,我们将创建两个具体的工厂 StrawberryCakeFactory 和 MangoCakeFactory,它们分别实现了 CakeFactory 类中定义的方法。
// 抽象工厂
class CakeFactory {
createCake() {
throw new Error("createCake() must be overridden");
}
createDecoration() {
throw new Error("createDecoration() must be overridden");
}
}
// 具体工厂 - 草莓蛋糕工厂
class StrawberryCakeFactory extends CakeFactory {
createCake() {
return new StrawberryCake();
}
createDecoration() {
return "Strawberries";
}
}
// 具体工厂 - 芒果蛋糕工厂
class MangoCakeFactory extends CakeFactory {
createCake() {
return new MangoCake();
}
createDecoration() {
return "Mango slices";
}
}
// 客户端代码
function makeCake(factory) {
const cake = factory.createCake();
const decoration = factory.createDecoration();
cake.makeCake();
console.log(`Decorating with: ${decoration}`);
}
// 使用抽象工厂
const strawberryFactory = new StrawberryCakeFactory();
makeCake(strawberryFactory);
const mangoFactory = new MangoCakeFactory();
makeCake(mangoFactory);
上面两段代码展示了模板方法模式和抽象工厂模式的不同应用场景和核心点。让我们来分析它们的主要差异:
模板方法模式的核心点:
-
定义了一个算法的骨架(makeCake 方法),并允许子类(StrawberryCake 和 MangoCake)在不改变算法结构的情况下,重写算法的某些特定步骤(prepareIngredients 和 decorate 方法)。
-
通过继承来实现代码复用和扩展功能。子类继承了父类 Cake 并提供了特定步骤的具体实现。
-
客户端直接与具体的子类实例交互,子类实例控制了算法的某些特定步骤。
抽象工厂模式的核心点:
-
提供了一个接口(CakeFactory)用于创建一系列相关或依赖的对象(createCake 和 createDecoration 方法),而不需要指定它们具体的类。
-
通过对象组合来实现功能的扩展。具体工厂(StrawberryCakeFactory 和 MangoCakeFactory)实现了工厂接口并创建了具体的产品。
-
客户端代码(makeCake 函数)与抽象工厂接口交互,而不是直接与具体产品交云。这允许在不改变客户端代码的情况下更换工厂和产品。
策略模式 (Strategy Pattern)
定义一系列算法,并使它们可以互换——该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户,即:策略模式允许算法独立于使用它们的客户端而变化。
典型场景:
-
当需要在运行时更改算法时。例如,在排序算法中,可以使用策略模式来选择不同的排序算法。
-
导航软件中的路线规划,可以根据用户的选择(最快、最短、避开收费路段)来选择不同的算法。
策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式结构
策略模式的主要角色如下:
-
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
-
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
-
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
策略模式代码示例:
// 策略对象定义不同的验证算法
const validationStrategies = {
isNonEmpty: {
validate: (value) => value !== '',
message: 'The value cannot be empty'
},
isNumber: {
validate: (value) => !isNaN(parseFloat(value)) && isFinite(value),
message: 'The value must be a number'
},
isEmail: {
validate: (value) => /\S+@\S+\.\S+/.test(value),
message: 'The value must be an email address'
}
};
// Context 类,使用策略对象的 validate 方法
class Validator {
constructor() {
this.errors = [];
this.validationStrategies = {};
}
// 添加验证规则
addValidation(field, strategy) {
this.validationStrategies[field] = strategy;
}
// 执行验证
validate(data) {
this.errors = [];
for (const field in this.validationStrategies) {
const strategy = this.validationStrategies[field];
if (!strategy.validate(data[field])) {
this.errors.push({ field: field, error: strategy.message });
}
}
return this.errors.length === 0;
}
// 获取验证错误信息
getErrors() {
return this.errors;
}
}
// 使用策略模式的客户端代码
const validator = new Validator();
validator.addValidation('username', validationStrategies.isNonEmpty);
validator.addValidation('age', validationStrategies.isNumber);
validator.addValidation('email', validationStrategies.isEmail);
const data = {
username: 'JohnDoe',
age: '30',
email: 'johndoe@example.com'
};
const isValid = validator.validate(data);
if (isValid) {
console.log('Validation passed');
} else {
console.error('Validation failed:', validator.getErrors());
}
下面的代码改为typescript(纯种的策略模式结构)
// 抽象策略(Strategy)接口
interface ValidationStrategy {
validate(value: string): boolean;
errorMessage: string;
}
// 具体策略(Concrete Strategy)类 - 非空验证
class NonEmptyValidation implements ValidationStrategy {
errorMessage = 'The value cannot be empty';
validate(value: string): boolean {
return value !== '';
}
}
// 具体策略(Concrete Strategy)类 - 数字验证
class NumberValidation implements ValidationStrategy {
errorMessage = 'The value must be a number';
validate(value: string): boolean {
return !isNaN(parseFloat(value)) && isFinite(value as any);
}
}
// 具体策略(Concrete Strategy)类 - 邮箱验证
class EmailValidation implements ValidationStrategy {
errorMessage = 'The value must be an email address';
validate(value: string): boolean {
return /\S+@\S+\.\S+/.test(value);
}
}
// 环境(Context)类
class Validator {
private errors: string[] = [];
private validationStrategies: Map<string, ValidationStrategy> = new Map();
// 添加验证规则
addValidation(field: string, strategy: ValidationStrategy): void {
this.validationStrategies.set(field, strategy);
}
// 执行验证
validate(data: { [key: string]: string }): boolean {
this.errors = [];
for (const [field, strategy] of this.validationStrategies) {
if (!strategy.validate(data[field])) {
this.errors.push(`${field}: ${strategy.errorMessage}`);
}
}
return this.errors.length === 0;
}
// 获取验证错误信息
getErrors(): string[] {
return this.errors;
}
}
// 客户端代码
const validator = new Validator();
validator.addValidation('username', new NonEmptyValidation());
validator.addValidation('age', new NumberValidation());
validator.addValidation('email', new EmailValidation());
const data = {
username: 'JohnDoe',
age: '30',
email: 'johndoe@example.com'
};
const isValid = validator.validate(data);
if (isValid) {
console.log('Validation passed');
} else {
console.error('Validation failed:', validator.getErrors());
}
具体推荐阅读
设计模式(十八)----行为型模式之策略模式 https://www.cnblogs.com/xiaoyh/p/16560074.html
观察者模式 (Observer Pattern)
定义对象之间的一对多依赖关系,以便当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
典型场景:
当需要松散耦合对象并允许它们在不了解彼此的情况下进行通信时。例如,
-
在 GUI 中,当用户更改文本框中的文本时,观察者模式可以通知所有依赖该文本框的控件。
-
新闻订阅服务,当新闻更新时,所有订阅者都会收到通知。
具体推荐阅读
迭代器模式 (Iterator Pattern)
提供一种方法来顺序访问集合中的元素,而不暴露集合的底层表示。
典型场景:
当需要遍历集合中的元素,但又不想直接访问集合的内部结构时。例如
-
编程语言解释器或表达式计算器,解释特定的语法或表达式。
-
在 Java 中,迭代器模式用于遍历集合中的元素。
-
jQuery 的 $.each() 函数也是一个迭代器。它可以遍历任何类型的 jQuery 对象,包括 DOM 元素、HTML 字符串、JSON 对象甚至是普通的 JavaScript 对象。
迭代器模式结构
迭代器模式主要包含以下角色:
-
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
-
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
-
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
-
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
迭代器的代码实现:
// 定义一个迭代器接口
interface Iterator<T> {
hasNext(): boolean;
next(): T;
}
// 定义一个具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
private collection: T[];
private index: number = 0;
constructor(collection: T[]) {
this.collection = collection;
}
hasNext(): boolean {
return this.index < this.collection.length;
}
next(): T {
return this.collection[this.index++];
}
}
// 定义一个聚合接口
interface Aggregate<T> {
createIterator(): Iterator<T>;
}
// 定义一个具体聚合
class ConcreteAggregate<T> implements Aggregate<T> {
private collection: T[];
constructor(collection: T[]) {
this.collection = collection;
}
createIterator(): Iterator<T> {
return new ConcreteIterator(this.collection);
}
}
// 使用迭代器模式
const aggregate = new ConcreteAggregate([1, 2, 3, 4, 5]);
const iterator = aggregate.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
具体参看:
设计模式(二十四)----行为型模式之迭代器模式 https://www.cnblogs.com/xiaoyh/p/16563331.html
责任链模式 (Chain of Responsibility Pattern)
将请求链式传递给一系列处理程序,直到有一个处理程序处理该请求。
典型场景:
当需要将请求处理委托给多个对象,并且处理顺序不重要时。例如
-
客户支持系统,请求可以在多个处理者之间传递,直到找到合适的处理者。
-
在 Web 服务器中,责任链模式可以将请求传递给一系列过滤器,每个过滤器都可以处理请求的特定方面。
责任链模式的角色:
-
抽象处理角色(Handler):定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
-
具体处理者角色(ConcreteHandler):实现抽象处理者的处理方法,该处理方法中会进行判断能够处理本次请求,如果可以则将请求转给其后继者继续执行处理方法。
-
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
typescript 代码实现:
// 抽象处理者
abstract class Approver {
protected successor: Approver | null = null;
// 设置下一个处理者
public setSuccessor(successor: Approver): void {
this.successor = successor;
}
// 处理请求的方法,由子类实现
public abstract handleRequest(leaveDays: number): void;
}
// 具体处理者 - 组织领导
class TeamLeader extends Approver {
public handleRequest(leaveDays: number): void {
if (leaveDays <= 2) {
console.log(`TeamLeader approved ${leaveDays} day(s) leave.`);
} else if (this.successor) {
this.successor.handleRequest(leaveDays);
}
}
}
// 具体处理者 - 部门领导
class DepartmentManager extends Approver {
public handleRequest(leaveDays: number): void {
if (leaveDays <= 5) {
console.log(`DepartmentManager approved ${leaveDays} day(s) leave.`);
} else if (this.successor) {
this.successor.handleRequest(leaveDays);
}
}
}
// 具体处理者 - 总经理
class GeneralManager extends Approver {
public handleRequest(leaveDays: number): void {
if (leaveDays <= 10) {
console.log(`GeneralManager approved ${leaveDays} day(s) leave.`);
} else {
console.log(`Leave request for ${leaveDays} day(s) requires an executive meeting.`);
}
}
}
// 客户端代码
const teamLeader = new TeamLeader();
const departmentManager = new DepartmentManager();
const generalManager = new GeneralManager();
// 设置责任链
teamLeader.setSuccessor(departmentManager);
departmentManager.setSuccessor(generalManager);
// 发起请假请求
teamLeader.handleRequest(2); // TeamLeader can handle this request
teamLeader.handleRequest(4); // DepartmentManager has to handle this request
teamLeader.handleRequest(8); // GeneralManager has to handle this request
teamLeader.handleRequest(12); // Needs executive meeting
之前我觉得洋葱模型就是挺适合责任链模式的,,其是不是的!。
适合责任链模式的架构模式是:
-
管道模式:管道模式将请求按顺序传递给一系列过滤器,每个过滤器都可以修改请求或返回响应。
-
拦截器模式:拦截器模式允许在请求和响应被处理之前和之后插入自定义逻辑。
-
中间件模式:每个中间件组件都有机会处理请求并决定是否将请求传递给链中的下一个中间件。这在Redux中的中间件或Express.js中的请求处理中很常见。
-
插件模式:。每个插件都可以独立地处理任务,并决定是否继续执行后续插件。
具体参看:
-
如何评价责任链模式应用的场景? - 东小西的回答 - 知乎 https://www.zhihu.com/question/485478970/answer/2685506927
中介者模式 (Mediator Pattern)
定义一个对象,它封装了多个对象之间的交互。中介者模式使对象之间松散耦合,并允许它们独立于彼此进行通信。
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
核心思想
如果一个系统中对象之间的联系呈现出网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象称为 "同事对象"。我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将同事对象之间的关系行为进行分离和封装,使之成为一个松耦合的系统。
本质
解耦各个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。通过中介者对象统一管理这些交互关系,并且还可以在同事对象的逻辑上封装自己的逻辑。
典型场景:
当需要控制多个对象之间的复杂交互时。例如
-
在聊天室中,中介者模式可以管理用户之间的消息传递。
-
Redux 、Vuex、 MobX 都可以说是中介模式:使用中介者模式来管理应用程序状态(store 作为中介者,负责协调应用程序中的所有状态更新)。
结构
中介者模式包含以下主要角色:
-
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
-
具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
-
抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
-
具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
// 中介者接口
interface Mediator {
notify(sender: object, event: string): void;
}
// 具体中介者
class ConcreteMediator implements Mediator {
private component1: Component1;
private component2: Component2;
constructor(c1: Component1, c2: Component2) {
this.component1 = c1;
this.component1.setMediator(this);
this.component2 = c2;
this.component2.setMediator(this);
}
public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.component2.doC();
} else if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:');
this.component1.doB();
this.component2.doC();
}
}
}
// 基础组件
class BaseComponent {
protected mediator: Mediator;
constructor(mediator?: Mediator) {
this.mediator = mediator!;
}
public setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
// 具体组件1
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.');
this.mediator.notify(this, 'A');
}
public doB(): void {
console.log('Component 1 does B.');
this.mediator.notify(this, 'B');
}
}
// 具体组件2
class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.');
this.mediator.notify(this, 'C');
}
public doD(): void {
console.log('Component 2 does D.');
this.mediator.notify(this, 'D');
}
}
// 客户端代码
const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);
console.log('Client triggers operation A.');
c1.doA();
console.log('Client triggers operation D.');
c2.doD();
在这个示例中,ConcreteMediator 类通过实现 Mediator 接口充当中介者。Component1 和 Component2 是两个基于 BaseComponent 的具体组件,它们通过中介者进行通信,而不是直接相互调用。当一个组件改变状态或需要与其他组件通信时,它会通过中介者来协调这些交互。
适合中介者模式的架构:
-
复杂的前端应用,特别是那些需要多个组件相互通信的单页应用(SPA)。
-
当组件或类之间的通信非常复杂且相互依赖时,使用中介者模式可以将这些通信集中到一个中介者对象中,从而简化它们之间的交互。
-
在微服务架构中,中介者可以作为服务总线或消息代理,协调不同服务之间的通信。
中介者模式适用于需要降低多个组件或服务之间复杂交云的情况,它有助于将系统的通信复杂性集中管理,从而使系统更加模块化和易于维护。
中介者模式VS观察者模式
-
通信结构:
-
中介者模式通常用于集中控制通信,所有组件都通过中介者来交云,
-
观察者模式通常用于分散控制,主题对象直接通知其所有的观察者。
-
耦合度:
-
中介者模式将组件之间的通信耦合转移到了中介者对象上,
-
观察者模式则是在主题和观察者之间建立了一种松散耦合。
-
适用性:
-
中介者模式适合用于减少多个组件或类之间的直接交云
-
观察者模式适合用于状态变化需要通知多个依赖者的场景。
具体参看文章:
设计模式(二十三)----行为型模式之中介者模式 https://www.cnblogs.com/xiaoyh/p/16563324.html
访问者模式 (Visitor Pattern)
将操作与对象结构分离,以便可以在不改变结构的情况下向对象添加新操作。
访问者模式能将算法与其所作用的对象隔离开来。
访问者模式的适用场景:
-
当需要在一个复杂对象结构中执行与其元素无关的操作时,可以使用访问者模式。这允许在不修改元素类的情况下添加新的操作。
-
当需要对不同类型的对象执行不同的操作,且不希望在对象本身实现这些操作时,可以使用访问者模式。这可以避免在对象中添加大量条件语句。
-
当需要分离一组类的操作逻辑时,可以使用访问者模式。通过将操作实现移动到访问者类中,可以使原始类更加简单,更易于维护。
典型场景:
当需要在不修改对象结构的情况下向对象添加新操作时。例如,
-
文档格式转换器,可以在不改变文档元素的情况下添加新的操作,以支持不同的格式输出。
-
编译器设计: 在编译器中,访问者模式可以用于遍历抽象语法树(AST)并执行不同的操作,比如语法检查、语义分析、代码生成等。
-
文档解析和处理: 当需要对复杂的文档结构进行解析和处理时,访问者模式可以用于遍历文档元素并执行不同的操作,比如格式转换、信息提取等。
-
图形化界面库: 在图形化界面库中,访问者模式可以用于处理图形元素的遍历和操作,比如在 GUI 中实现布局、事件处理等功能。
-
网络协议解析: 在网络编程中,访问者模式可以用于解析和处理复杂的协议数据包,执行不同的操作,如数据包解析、错误检测等。
访问者模式优缺点
访问者模式的优点:
-
分离操作和数据结构:访问者模式允许将操作逻辑与数据结构分离,使得操作逻辑易于维护和扩展。
-
单一职责原则:每个访问者对象负责一组特定的操作,使得操作实现更加清晰和简洁。
-
扩展性:通过添加新的访问者类,可以很容易地向现有对象结构添加新功能,而无需修改原始类。
访问者模式的缺点:
-
对象结构不稳定:如果对象结构频繁变化,访问者模式可能不适合。因为每次添加新的元素类,都需要修改访问者接口及其所有实现。
-
访问者类可能变得复杂:如果访问者需要处理许多不同类型的元素,那么访问者类可能会变得很复杂。这会导致难以维护和理解的代码。
-
破坏封装:访问者模式需要暴露对象结构的内部细节给访问者对象,这可能会导致对象结构的封装性受到破坏。
-
不适用于所有场景:访问者模式主要适用于对象结构稳定且需要对其执行多种操作的场景。对于需要频繁修改对象结构的项目,访问者模式可能并不适用。
结构组成
-
抽象访问者(Visitor): 定义了对每个具体元素类所提供的访问操作接口。
-
具体访问者(ConcreteVisitor): 实现了抽象访问者定义的接口,提供了具体的访问操作。
-
抽象元素(Element): 声明了接受访问者操作的接口。
-
具体元素(ConcreteElement): 实现了抽象元素声明的接口,提供了接受访问者操作的具体实现。
-
对象结构(Object Structure): 包含了具体元素的容器,可以是一个集合或一个复杂的结构,用于存放具体元素。
-
客户端(Client): 通过访问者模式访问对象结构中的元素。
typescript代码的简单实现
/// 访问者接口
interface Visitor {
visitConcreteElementA(element: ConcreteElementA): void;
visitConcreteElementB(element: ConcreteElementB): void;
}
// 具体访问者
class ConcreteVisitor1 implements Visitor {
public visitConcreteElementA(element: ConcreteElementA): void {
console.log(`ConcreteVisitor1: Visiting ${element.operationA()}`);
}
public visitConcreteElementB(element: ConcreteElementB): void {
console.log(`ConcreteVisitor1: Visiting ${element.operationB()}`);
}
}
// 另一个具体访问者
class ConcreteVisitor2 implements Visitor {
public visitConcreteElementA(element: ConcreteElementA): void {
console.log(`ConcreteVisitor2: Visiting ${element.operationA()}`);
}
public visitConcreteElementB(element: ConcreteElementB): void {
console.log(`ConcreteVisitor2: Visiting ${element.operationB()}`);
}
}
// 元素接口
interface Element {
accept(visitor: Visitor): void;
}
// 具体元素A
class ConcreteElementA implements Element {
public accept(visitor: Visitor): void {
visitor.visitConcreteElementA(this);
}
public operationA(): string {
return 'ConcreteElementA';
}
}
// 具体元素B
class ConcreteElementB implements Element {
public accept(visitor: Visitor): void {
visitor.visitConcreteElementB(this);
}
public operationB(): string {
return 'ConcreteElementB';
}
}
// 客户端代码
const elements: Element[] = [
new ConcreteElementA(),
new ConcreteElementB(),
];
const visitor1: Visitor = new ConcreteVisitor1();
const visitor2: Visitor = new ConcreteVisitor2();
elements.forEach((element) => {
element.accept(visitor1);
element.accept(visitor2);
});
具体的例子:
// 访问者接口
interface ComputerPartVisitor {
visitKeyboard(keyboard: Keyboard): void;
visitMouse(mouse: Mouse): void;
visitMonitor(monitor: Monitor): void;
}
// 计算机外设接口
interface ComputerPart {
accept(visitor: ComputerPartVisitor): void;
}
// 键盘类
class Keyboard implements ComputerPart {
public accept(visitor: ComputerPartVisitor): void {
visitor.visitKeyboard(this);
}
public type(): void {
console.log('Keyboard typing...');
}
}
// 鼠标类
class Mouse implements ComputerPart {
public accept(visitor: ComputerPartVisitor): void {
visitor.visitMouse(this);
}
public click(): void {
console.log('Mouse clicking...');
}
}
// 显示器类
class Monitor implements ComputerPart {
public accept(visitor: ComputerPartVisitor): void {
visitor.visitMonitor(this);
}
public display(): void {
console.log('Monitor displaying...');
}
}
// 通用计算机外设访问者
class GeneralComputerPartVisitor implements ComputerPartVisitor {
public visitKeyboard(keyboard: Keyboard): void {
keyboard.type();
}
public visitMouse(mouse: Mouse): void {
mouse.click();
}
public visitMonitor(monitor: Monitor): void {
monitor.display();
}
}
// 显示器访问者
class MonitorVisitor implements ComputerPartVisitor {
public visitKeyboard(keyboard: Keyboard): void {
// 不关心键盘
}
public visitMouse(mouse: Mouse): void {
// 不关心鼠标
}
public visitMonitor(monitor: Monitor): void {
console.log('Applying special monitor calibration...');
monitor.display();
}
}
// 客户端代码
const parts: ComputerPart[] = [
new Keyboard(),
new Mouse(),
new Monitor(),
];
const generalVisitor: ComputerPartVisitor = new GeneralComputerPartVisitor();
const monitorVisitor: ComputerPartVisitor = new MonitorVisitor();
parts.forEach((part) => {
part.accept(generalVisitor); // 应用通用访问者
part.accept(monitorVisitor); // 应用显示器访问者
});
在这个示例中,ComputerPartVisitor 接口定义了访问者需要实现的方法,GeneralComputerPartVisitor 是一个通用的访问者,它对所有外设执行操作。MonitorVisitor 是一个专门为显示器设计的访问者,它只对显示器执行特殊的操作。
ComputerPart 接口定义了一个 accept 方法,Keyboard、Mouse 和 Monitor 是实现了该接口的具体外设类。每个外设类都实现了 accept 方法,该方法接受一个访问者并调用其访问方法。
具体参看:
-
【设计模式】访问者模式 https://zhuanlan.zhihu.com/p/401974000
-
[设计模式]24. 访问者模式 https://zhuanlan.zhihu.com/p/448513112
-
操作复杂对象结构——访问者模式(一) https://www.bookstack.cn/read/design-pattern-java/操作复杂对象结构——访问者模式(一).md
解释器模式 (Interpreter Pattern)
定义一个语言的文法,并提供一个解释器来解释该语言中的句子。
典型场景:
当需要解释一个特定领域的语言时。例如,
-
编程语言解释器或表达式计算器,解释特定的语法或表达式。
-
在编译器中,解释器模式可以解释源代码中的语句。
在解释器模式结构图中包含如下几个角色:
-
AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
-
TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
-
NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
-
Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。
typescript实现
// 表达式接口
interface Expression {
interpret(): number;
}
// 数字表达式
class NumberExpression implements Expression {
private value: number;
constructor(value: number) {
this.value = value;
}
public interpret(): number {
return this.value;
}
}
// 加法表达式
class AddExpression implements Expression {
private leftExpression: Expression;
private rightExpression: Expression;
constructor(left: Expression, right: Expression) {
this.leftExpression = left;
this.rightExpression = right;
}
public interpret(): number {
return this.leftExpression.interpret() + this.rightExpression.interpret();
}
}
// 减法表达式
class SubtractExpression implements Expression {
private leftExpression: Expression;
private rightExpression: Expression;
constructor(left: Expression, right: Expression) {
this.leftExpression = left;
this.rightExpression = right;
}
public interpret(): number {
return this.leftExpression.interpret() - this.rightExpression.interpret();
}
}
// 客户端代码
const expression: Expression = new AddExpression(
new NumberExpression(5),
new SubtractExpression(
new NumberExpression(10),
new NumberExpression(3)
)
);
console.log(expression.interpret()); // 输出结果应该是 5 + (10 - 3) = 12
解释器模式可能与以下几种设计模式混淆:
-
命令模式(Command Pattern):
-
相似之处:命令模式和解释器模式都涉及到对操作的封装。
-
区别:
-
命令模式封装了一系列操作和参数,使得可以将请求作为一个对象存储、传递和调用。
-
解释器模式则是用于定义一个语言的文法,并提供一个解释器来解释语言中的句子。
-
如何区分:
-
如果目的是创建一个可以解释和执行预定义语法和规则的系统,那么使用解释器模式。
-
如果目的是将操作封装为对象,以便支持撤销操作、日志记录或事务等,则使用命令模式。
-
策略模式(Strategy Pattern):
-
相似之处:策略模式和解释器模式都允许在运行时更改对象的行为。
-
区别:
-
策略模式通过定义一系列可互换的算法来允许对象改变其行为,
-
解释器模式专注于定义和解释语言的文法。
-
如何区分:
-
如果你需要在不同算法之间切换以完成相同的任务,那么使用策略模式。
-
如果你需要实现一个特定的语言解释器,那么使用解释器模式。
-
访问者模式(Visitor Pattern):
-
相似之处:访问者模式和解释器模式都涉及到对对象结构的操作。
-
区别:
-
访问者模式主要用于在不改变元素类的情况下增加操作,它通过将操作逻辑外部化到访问者中来实现。
-
解释器模式则是用于解释给定语言的句子。
-
如何区分:如果你需要对一个复杂的对象结构执行多种不同且不相关的操作,并且希望能够在不修改对象结构的情况下添加新操作,那么使用访问者模式。如果你需要实现一个解释器来解释语言的句子,那么使用解释器模式。
-
组合模式(Composite Pattern):
-
相似之处:组合模式和解释器模式都可以用来表示对象的部分-整体层次结构。
-
区别:
-
组合模式主要用于表示和管理对象的层次结构,使得客户端可以统一对待单个对象和组合对象。
-
解释器模式则用于定义语言的文法和解释句子。
-
如何区分:
-
如果你的目标是简化对单个对象和组合对象的操作,那么使用组合模式。
-
如果你的目标是实现一个解释器来解释语言的句子,那么使用解释器模式。
状态模式 (State Pattern)
允许对象在内部状态改变时改变其行为。
状态模式与有限状态机的概念紧密相关。
典型场景:
当对象的内部状态会影响其行为时。例如,
-
在状态机中,状态模式可以定义对象在不同状态下的行为。
-
文本编辑器中的文本选择工具,根据选择的工具(如选择、绘制、擦除)改变行为。
结构
状态模式包含以下主要角色。
-
环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
-
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
-
具体状态(Concrete State)角色:实现抽象状态所对应的行为。
typescript 代码实现:
// 状态接口
interface State {
handle(context: Context): void;
}
// 具体状态A
class ConcreteStateA implements State {
public handle(context: Context): void {
console.log('Handling state A.');
context.setState(new ConcreteStateB());
}
}
// 具体状态B
class ConcreteStateB implements State {
public handle(context: Context): void {
console.log('Handling state B.');
context.setState(new ConcreteStateA());
}
}
// 上下文类
class Context {
private state: State;
constructor(state: State) {
this.state = state;
}
public setState(state: State): void {
this.state = state;
}
public request(): void {
this.state.handle(this);
}
}
// 客户端代码
const context = new Context(new ConcreteStateA());
context.request(); // Handling state A.
context.request(); // Handling state B.
context.request(); // Handling state A.
前端开源代码案例:
在前端开发中,状态模式可以用于管理组件或应用的状态。例如,Redux库就是一个状态管理库,它在某种程度上体现了状态模式的思想。在Redux中,状态转换逻辑通过纯函数(reducers)来管理,每个reducer根据当前状态和传入的action来计算新状态。
// Redux中的reducer示例
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
在这个例子中,visibilityFilter 函数是一个reducer,它根据当前状态和action来返回新状态,这与状态模式中的状态转换逻辑类似。
推荐本站的:《状态机图教程 (UML State Diagram) 》、《从有限状态机、图灵机到现代计算机——排版整理笔记版》、《React 源码剖析系列—生命周期的管理艺术—有限状态机》
状态模式和策略模式的区别
-
状态模式强调的是一个对象内部状态的改变会导致其行为的改变,状态是对象的内部属性,通常由对象自身管理和控制。
-
策略模式强调的是一系列算法的可互换性,策略是从对象外部传入的,通常由客户端代码控制和管理。
状态模式和策略模式都可以避免多重条件选择语句,它们的类图也十分相似,它们都有一个上下文(Context)、一些策略类(Strategy)或者状态类(State),上下文把请求委托给这些类来执行。
如果只是单纯的多情况if else ,各个条件之间是平等又平行的,没有任何联系,就可以选择使用策略模式,代码可能会更优雅易维护,成本会低一些,策略模式要求了解策略类中的算法逻辑,以便它们之间的互相替换;状态模式中,状态和状态对应的行为以及状态间的切换是早就规定好的,都是在内部发生的,不需要了解具体的细节,类似IM socket的场景,状态机会更加优雅。
具体参看:
-
没有彻底消除if-else还能算是状态模式吗? - 清泓y的回答 - 知乎 https://www.zhihu.com/question/537269355/answer/3203869280
-
浅谈状态模式和状态机 https://zhuanlan.zhihu.com/p/408738144
如果只是单纯的多情况if else ,各个条件之间是平等又平行的,没有任何联系,就可以选择使用策略模式,代码可能会更优雅易维护,成本会低一些,策略模式要求了解策略类中的算法逻辑,以便它们之间的互相替换;状态模式中,状态和状态对应的行为以及状态间的切换是早就规定好的,都是在内部发生的,不需要了解具体的细节,类似IM socket的场景,状态机会更加优雅。
备忘录模式 (Memento Pattern)
捕获一个对象的内部状态,以便以后可以恢复该状态。
典型场景:
当需要保存和恢复对象的内部状态时。例如,
-
游戏存档,保存当前游戏状态,以便可以重新加载。
-
在撤消操作中,备忘录模式可以保存对象的先前状态,以便可以撤消对该对象的更改。
备忘录模式结构图中包含如下几个角色:
-
Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
-
Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
-
Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
理解备忘录模式并不难,但关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。
typescript代码实现
// 备忘录类
class ChessMemento {
private state: string;
constructor(state: string) {
this.state = state;
}
public getState(): string {
return this.state;
}
}
// 发起人类
class ChessGame {
private state: string;
constructor() {
this.state = ''; // 初始状态,可以是棋盘的表示等
}
public playMove(move: string): void {
// 执行棋步并更新状态
this.state += move + ';';
}
public save(): ChessMemento {
// 保存当前状态
return new ChessMemento(this.state);
}
public restore(memento: ChessMemento): void {
// 恢复到之前的状态
this.state = memento.getState();
}
public showState(): void {
console.log(this.state);
}
}
// 管理者类
class ChessCaretaker {
private mementos: ChessMemento[] = [];
public saveMemento(memento: ChessMemento): void {
this.mementos.push(memento);
}
public getMemento(index: number): ChessMemento {
return this.mementos[index];
}
}
// 客户端代码
const game = new ChessGame();
const caretaker = new ChessCaretaker();
// 玩家下棋步
game.playMove('e4');
game.playMove('e5');
caretaker.saveMemento(game.save()); // 保存当前状态
game.playMove('Nf3');
caretaker.saveMemento(game.save()); // 保存新状态
// 悔棋操作
game.restore(caretaker.getMemento(0)); // 恢复到第一次保存的状态
game.showState(); // 显示当前棋盘状态
如果用解释器来写呢?
// 定义一个抽象表达式接口
interface Expression {
interpret(): void;
}
// 定义一个具体表达式(移动棋子)
class MoveExpression implements Expression {
private chessboard: Chessboard;
private from: string;
private to: string;
constructor(chessboard: Chessboard, from: string, to: string) {
this.chessboard = chessboard;
this.from = from;
this.to = to;
}
interpret(): void {
this.chessboard.move(this.from, this.to);
}
}
// 定义一个具体表达式(悔棋)
class UndoExpression implements Expression {
private chessboard: Chessboard;
constructor(chessboard: Chessboard) {
this.chessboard = chessboard;
}
interpret(): void {
this.chessboard.undo();
}
}
// 定义一个解释器(棋盘)
class Chessboard {
private board: string[][];
constructor() {
this.board = [['♜', '♞', '♝', '♛', '♚', '♝', '♞', '♜'],
['♟', '♟', '♟', '♟', '♟', '♟', '♟', '♟'],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
['♙', '♙', '♙', '♙', '♙', '♙', '♙', '♙'],
['♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖']];
}
move(from: string, to: string): void {
// 移动棋子
}
undo(): void {
// 悔棋
}
interpret(expression: Expression): void {
expression.interpret();
}
}
// 使用解释器模式
const chessboard = new Chessboard();
const moveExpression = new MoveExpression(chessboard, 'e2', 'e4');
const undoExpression = new UndoExpression(chessboard);
chessboard.interpret(moveExpression); // 移动棋子
chessboard.interpret(undoExpression); // 悔棋
状态模式(State Pattern)和备忘录模式(Memento Pattern)都是行为设计模式,它们在处理对象状态方面有所涉及,但它们的目的、实现方式和应用场景有显著的不同。
状态模式
-
目的:状态模式允许一个对象在其内部状态改变时改变它的行为。对象会根据自身的状态变化来选择不同的行为方式。
-
应用场景:当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,状态模式是合适的。状态模式通常用于实现有状态的对象,其中对象的行为会随着状态的改变而改变。
-
实现方式:状态模式通过定义一个状态接口和一系列实现该接口的具体状态类来实现。上下文对象通过持有状态接口的引用来维护当前状态,并将状态相关的行为委托给当前状态对象处理。
备忘录模式
-
目的:备忘录模式用于捕获和恢复对象的内部状态,而不暴露其实现细节。这种模式特别适用于实现撤销(undo)和恢复(redo)功能。
-
应用场景:当需要保存对象的历史状态,并且可能需要在将来某个时刻恢复这些状态时,备忘录模式是合适的。备忘录模式通常用于实现撤销和恢复操作,如编辑器的撤销功能或游戏的存档和加载。
-
实现方式:备忘录模式通过创建一个备忘录对象来保存当前状态,该备忘录对象对其他对象不可见,从而保证了封装性。发起人对象负责创建和使用备忘录来保存和恢复自身状态,而管理者对象负责保存备忘录的历史记录。
区别总结
-
设计意图不同:
-
状态模式关注于对象行为的变化,它通过状态对象来改变对象的行为。
-
备忘录模式关注于对象状态的保存和恢复,它通过备忘录对象来保存对象的历史状态。
-
应用场景不同:
-
状态模式适用于对象的状态会影响其行为的场景,
-
备忘录模式适用于需要保存和恢复对象状态的场景。
-
实现方式不同:
-
状态模式将每个状态的行为封装在独立的状态对象中,上下文对象通过这些状态对象来改变其行为。
-
备忘录模式通过备忘录对象来保存状态,而不改变对象的结构或暴露其实现细节。
尽管状态模式和备忘录模式都涉及对象的状态,但它们的设计目的和使用方式有明显的区别。状态模式关注于对象行为的动态变化,而备忘录模式关注于对象状态的保存和恢复。
围棋,状态模式
// 状态接口
interface ChessState {
playMove(move: string): void;
showState(): void;
}
// 具体状态类
class ChessMoveState implements ChessState {
private moves: string[] = [];
constructor(moves?: string[]) {
if (moves) {
this.moves = moves.slice(); // 创建移动列表的副本
}
}
public playMove(move: string): void {
this.moves.push(move);
}
public showState(): void {
console.log(this.moves.join('; '));
}
public clone(): ChessMoveState {
// 创建当前状态的副本
return new ChessMoveState(this.moves);
}
}
// 上下文类
class ChessGame {
private currentState: ChessMoveState;
constructor() {
this.currentState = new ChessMoveState();
}
public playMove(move: string): void {
this.currentState.playMove(move);
}
public save(): ChessMoveState {
return this.currentState.clone();
}
public restore(state: ChessMoveState): void {
this.currentState = state.clone();
}
public showState(): void {
this.currentState.showState();
}
}
// 客户端代码
const game = new ChessGame();
const savedStates: ChessMoveState[] = [];
// 玩家下棋步
game.playMove('e4');
game.playMove('e5');
savedStates.push(game.save()); // 保存当前状态
game.playMove('Nf3');
savedStates.push(game.save()); // 保存新状态
// 悔棋操作
game.restore(savedStates[0]); // 恢复到第一次保存的状态
game.showState(); // 显示当前棋盘状态
命令模式 (Command Pattern)
将请求封装成对象,从而使您可以用不同的方式对请求进行参数化、排队和执行。
典型场景:
当需要将请求与执行请求的对象解耦时。例如
-
遥控器按钮,每个按钮执行不同的命令,如开/关电视、调节音量等。
-
在文本编辑器中,命令模式可以将编辑操作(如剪切、复制和粘贴)封装成对象,以便可以轻松地撤消和重做这些操作。
命令模式结构图中包含如下几个角色:
-
Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
-
ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
-
Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
-
Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
JavaScript代码实现
// 命令接口
interface Command {
execute(): void;
undo(): void;
}
// 电灯类
class Light {
public on(): void {
console.log('Light is on');
}
public off(): void {
console.log('Light is off');
}
}
// 风扇类
class Fan {
public start(): void {
console.log('Fan is running');
}
public stop(): void {
console.log('Fan is stopped');
}
}
// 电灯开关命令
class LightOnCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
public execute(): void {
this.light.on();
}
public undo(): void {
this.light.off();
}
}
// 电灯关命令
class LightOffCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
public execute(): void {
this.light.off();
}
public undo(): void {
this.light.on();
}
}
// 风扇开命令
class FanStartCommand implements Command {
private fan: Fan;
constructor(fan: Fan) {
this.fan = fan;
}
public execute(): void {
this.fan.start();
}
public undo(): void {
this.fan.stop();
}
}
// 风扇关命令
class FanStopCommand implements Command {
private fan: Fan;
constructor(fan: Fan) {
this.fan = fan;
}
public execute(): void {
this.fan.stop();
}
public undo(): void {
this.fan.start();
}
}
// 调用者类
class RemoteControl {
private onCommands: Command[];
private offCommands: Command[];
private undoCommand: Command;
constructor() {
this.onCommands = [];
this.offCommands = [];
this.undoCommand = null!;
}
public setCommand(slot: number, onCommand: Command, offCommand: Command): void {
this.onCommands[slot] = onCommand;
this.offCommands[slot] = offCommand;
}
转载本站文章《再谈23种设计模式(3):行为型模式(学习笔记)》,
请注明出处:https://www.zhoulujun.cn/html/theory/engineering/model/9086.html