持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
面试官:说说你了解的设计模式。
Chain of Responsibility 责任链模式
定义
使多个对象都有机会处理请求,从而避免请求的发送者与请求处理者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
作用
责任链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,从而实现请求发送者与请求处理者的解耦。
角色
Handler(抽象处理者)
- 处理请求的接口,一般设计为具有抽象请求处理方法的抽象类,以便于不同的具体处理者进行继承,从而实现具体的请求处理方法。
- 由于每一个请求处理者的下家还是一个处理者,因此抽象处理者本身还包含了一个本身的引用作为其对下家的引用,以便将处理者链成一条链。
ConcreteHandler(具体处理者)
- 抽象处理者的子类,可以处理用户请求,其实现了抽象处理者中定义的请求处理方法。
- 在具体处理请求时需要进行判断,若其具有相应的处理权限,那么就处理它。
- 否则,其将请求转发给后继者,以便让后面的处理者进行处理。
优点
- 降低耦合度,使请求的发送者和接收者解耦,便于灵活的、可插拔的定义请求处理过程。
- 简化、封装了请求的处理过程,并且这个过程对客户端而言是透明的,以便于动态地重新组织链以及分配责任,增强请求处理的灵活性。
使用场景
- 多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定。
- 在请求处理者不明确的情况下向对个对象中的一个提交一个请求。
- 需要动态处理一组对象处理请求。
Command 命令模式
定义
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
角色
- 命令角色(Command):定义命令的接口,声明执行的方法。
- 具体命令角色(Concrete Command):实现命令接口,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 接收者角色(Receiver):负责具体实施和执行一个请求。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 请求者(调用者)角色(Invoker):负责调用命令对象执行请求。
- 客户角色(Client):创建一个具体命令对象并设定该命令对象的接收者。
优缺点
优点
- 命令模式的封装性很好:每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。
- 命令模式的扩展性很好,在命令模式中,在接收者类中一般会对操作进行最基本的封装,命令类则通过对这些基本的操作进行二次封装。
- 代码的复用性很好:当增加新命令的时候,对命令类的编写一般不是从零开始的,有大量的接收者类可供调用,也有大量的命令类可供调用。
缺点
- 命令如果很多,开发起来就要头疼了。
- 很多简单的命令,实现起来就几行代码的事,而使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装。
使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 在不同的时刻指定、排列和执行请求。
- 支持修改日志、撤销操作。
- 系统需要将一组操作组合在一起,即支持宏命令。
Interpreter 解释器模式
定义
给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。
角色
- 抽象解释器(AbstractExpression):具体的解释任务由各个实现类完成。
- 终结符表达式(TerminalExpression):实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结表达式,但有多个实例,对应不同的终结符。
- 非终结符表达式(NonterminalExpression):文法中的每条规则对应于一个非终结表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
- 上下文(Context): 上下文环境类,包含解释器之外的全局信息。
- 客户类(Test): 客户端,解析表达式,构建抽象语法树,执行具体的解释操作等。
优缺点
优点
扩展性强,若要新增乘,除,添加相应的非终结表达式,修改计算逻辑即可。
缺点
- 需要建大量的类,因为每一种语法都要建一个非终结符的类。
- 解释的时候采用递归调用方法,导致有时候函数的深度会很深,影响效率。
使用场景
- 有一个简单的语法规则,比如一个sql语句,如果我们需要根据sql语句进行rm转换,就可以使用解释器模式来对语句进行解释。
- 一些重复发生的问题,比如加减乘除四则运算,但是公式每次都不同,公式千变万化,但是都是由加减乘除四个非终结符来连接的,这时我们就可以使用解释器模式。
Iterator 迭代器模式
定义
提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
角色
- 抽象容器:一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
- 具体容器:就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
- 抽象迭代器:定义遍历元素所需要的方法。
- 取得第一个元素的方法first()。
- 取得下一个元素的方法next()。
- 判断是否遍历结束的方法isDone()(或者叫hasNext())。
- 移除当前对象的方法remove()。
- 迭代器实现:实现迭代器接口中定义的方法,完成集合的迭代。
优缺点
优点
- 简化了遍历方式。
- 可以提供多种遍历方式,用户只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
- 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
缺点
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
使用场景
- 内容保密 : 访问集合对象的内容, 无需暴露内部表示。
- 统一接口 : 为遍历不同的集合结构, 提供统一接口。
Mediator 中介者模式
定义
用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。
角色
- 抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信。一般包括一个或几个抽象的事件方法,并由子类去实现。
- 中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同事类。
- 同事类:如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。
优点
- 适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
- 使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
- 使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。
使用场景
对于那种同事类之间是网状结构的关系,可以使用中介者模式,将网状结构变为星状结构,使同事类之间的关系变的清晰一些。
Memento 备忘录模式
定义
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
角色
- Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
- Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。
- Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。
优缺点
优点
- 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
- 实现了对信息的封装,1个备忘录对象是1种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
缺点
资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
使用场景
- 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
- 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。