摘要:行为型:观察者、策略、状态、迭代器、模板方法、职责链、中介者、命令、备忘录、解释器和访问者
参考:
《设计模式之美》
JavaScript 设计模式核⼼原理与应⽤实践
Java设计模式:23种设计模式全面解析(超级详细)
JavaScript设计模式es6(23种)
23种设计模式——创建型设计模式(5种)
设计模式:可复用面向对象软件的基础
等等
全文中的组合关系即关联关系
观察者模式
观察者模式指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,观察者需要直接订阅目标事件,在目标发出内容改变的事件后,直接接收事件并作出响应。观察者模式降低了目标与观察者之间的耦合关系,但两者之间的依赖关系并没有完全解除。
发布订阅模式属于广义上的观察者模式,在发布订阅模式中,发布者和订阅者之间多了一个事件调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件。订阅者需要从调度中心订阅事件,避免了发布者和订阅者之间产生依赖关系。
通用实现流程:
1、创建抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
2、创建具体目标类,它实现抽象目标中的通知方法,当具体目标的内部状态发生改变时,通知所有注册过的观察者对象
3、创建抽象观察者类,它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体目标的更改通知时被调用
4、创建具体观察者类,实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
示例
1、工作群中有产品经理、技术人员 A、测试人员 B,产品经理定好开发安排并提醒其他人员开始新功能的开发
1 | // 发布者类 产品经理 |
2、Vue 数据双向绑定核心原理
1 | // 观察者 |
3、全局事件总线 Event Bus
1 | class EventEmitter { |
策略模式
策略模式定义一系列算法,将每个算法分别封装起来,让它们可以互相替换,且算法的变化不会影响使用算法的客户。策略模式的重心不是如何实现算法,而是如何组织这些算法,策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。环境类依赖策略类,但策略类不依赖环境类。可以通过策略模式来移除 if-else 分支判断,其本质是借助“查表法”的实现对象映射,根据 type 查表替代根据 type 分支判断。
通用实现流程:
1、创建抽象策略类,定义所有支持的算法的公共接口
2、创建具体策略类,封装所有算法,创建对象映射关系
3、使用映射关系的 type 替换 if-else 分支判断
示例
1、商品在不同促销活动的优惠程度不同
1 | // 抽象策略类 |
上面是标准的流程,每种策略都要有具体的类,但 JavaScript 有更简单的写法:将策略都设置为一个对象的方法
1 | // 策略对象 创建对象映射关系 |
2、验证表单
1 | <!DOCTYPE html> |
状态模式
状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。简单来说,就是当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。要注意的是:状态模式中的环境类与各个状态类之间是相互依赖的关系。策略模式是让用户指定更换的策略算法,也就是说客户端必须了解所有策略。而状态模式是状态在满足一定条件下的自动更换,用户无法指定状态,最多只能设置初始状态,客户端也就不需要了解状态,只需要调用方法即可。
通用实现流程:
1、创建抽象状态类,定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为
2、创建具体状态类,实现抽象状态所对应的行为,并且在需要的情况下进行状态切换
3、创建环境类,定义客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换
示例
1、红绿灯
1 | // 抽象状态类 |
2、咖啡机制作多种咖啡
1 | class CoffeeMaker { |
迭代器模式
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
通用实现流程:
1、创建抽象迭代器类,定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法
2、创建具体迭代器类,实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
3、创建抽象聚合类,定义存储、添加、删除聚合对象以及创建迭代器对象的接口
4、创建具体聚合类,实现抽象聚合类,返回一个具体迭代器的实例
示例
1、实现一个数组迭代器
1 | // 抽象迭代器类 |
2、使用 ES6 提供的生成器函数
1 | function* iteratorGenerator(arr) { |
3、迭代器生成函数(使用闭包保存并控制索引变化)
1 | // 定义生成器函数,入参是任意集合 |
模板方法模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。模板方法模式有两大作用:复用和扩展。复用指的是所有的子类可以复用父类中提供的模板方法的代码。扩展是指框架通过模板方法模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。同步回调看起来更像模板方法模式,异步回调更像观察者模式,但回调基于组合关系来实现,模板方法模式基于继承关系来实现,回调比模板方法模式更加灵活。
通用实现流程:
1、创建抽象模板类,实现模板方法和若干个基本方法。模板方法定义了算法的股价,按某种顺序调用其他的基本方法。基本方法则是整个算法中的一个步骤,可以有抽象类实现,也可以由子类继承重写
2、创建具体实现类,实现或重写抽象模板类中定义的方法
示例
冲咖啡、奶粉等饮料
1 | // 抽象模板类 |
职责链模式
职责链模式也叫责任链模式,它将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
通用实现流程:
1、创建抽象处理者类,定义一个处理请求的接口,包含抽象处理方法和一个后继连接
2、创建具体处理者类,继承并实现抽象类的处理方法,判断判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
3、创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
示例
请假审批,需要组长审批、经理审批、总监审批
1 | // 抽象处理者类 |
中介者模式
中介者模式定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。但当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
通用实现流程:
1、创建抽象中介者类,定义同事对象注册与转发同事对象信息的抽象方法
2、创建具体中介者类,实现中介者接口,定义一个列表来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色
3、创建抽象同事类,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
4、创建具体同事类,实现抽象类中的方法,当需要与其他同事对象交互时,由中介者对象负责后续的交互
示例
二手房交易平台
1 | // 抽象中介者类 中介公司 |
有两点可以精简优化:
1、将具体中介者对象实现成为单例
2、同事对象不持有中介者,在需要的时候直接获取中介者单例对象并调用
命令模式
命令模式将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。但是每一个命令都要设计一个具体命令类,增加了系统的复杂性。在代码实现上来看,就是两个对象之间又增加了一个包装对象(命令类),通过这个对象,将请求者和接收者解耦。
通用实现流程:
1、创建抽象命令类,定义执行命令的抽象方法
2、创建具体命令类,继承并实现抽象类中的方法,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作
3、创建接收者类,实现执行命令功能的相关操作,它是具体命令对象业务的真正实现者
4、创建请求者类,它是请求的发送者,通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者
示例
点餐
1 | // 抽象命令类 |
备忘录模式
备忘录模式又叫快照模式,它是指在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。但如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
通用实现流程:
1、创建发起人类,记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
2、创建备忘录类,负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
3、创建管理者类,对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
示例
编辑器恢复编辑的内容
1 | // 发起人类 |
解释器模式
解释器模式是指给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。要了解其中的几个概念:
1、文法是指用于描述语言的语法结构的形式规则。
2、句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。
3、语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。
4、终结符:通俗的说就是不能单独出现在推导式左边的符号,也就是说终结符不能再进行推导。
5、非终结符:不是终结符的都是非终结符。非终结符可理解为一个可拆分元素,而终结符是不可拆分的最小元素。
解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。解释器模式可以通过继承等机制来改变或扩展文法,而且每个表达式节点类都是相似的,实现起来较为容易。但因为解释器模式使用了大量的循环和递归调用,所以执行效率比较低。而且每条规则至少需要定义一个类,规则越多,系统越难以维护。在软件开发中的应用场景也比较少。解释器模式的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
通用实现流程:
1、创建抽象表达式类,定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret
2、创建终结符表达式类和非终结符表达式,它们是抽象表达式的子类,文法中的每一个终结符或非终结符都有一个具体终结表达式与之相对应
3、创建环境类,通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
示例
公交车读卡器播报并扣费
1 | // 抽象表达式类 |
访问者模式
访问者模式将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。实现访问者模式的关键是如何将作用于元素的操作分离出来封装成独立的类,而 JavaScript 是弱类型的语言,使用 call、apply 可以很简单的实现访问者模式。
通用实现流程:
1、创建抽象访问者类,定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素
2、创建具体访问者类,继承并实现抽象访问者类中的方法,确定访问者访问一个元素时该做什么
3、创建抽象元素类,声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数
4、创建具体元素类,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
5、创建对象结构类,提供让访问者对象遍历容器中的所有元素的方法
示例
1、艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以造出铜币,利用“纸”可以印出纸币。
1 | // 抽象访问者类 公司 |
2、让对象拥有像数组的 push 方法
1 | class Visitor { |