行为型
责任链模式
- 模式动机
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。 - 适用场景
一个请求的处理需要多个对象当中的一个或几个协作处理; - 类图
class ConcreteHandler1 extends Handler {
public ConcreteHandler1(Handler successor) {
super(successor);
}
@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE1) {
System.out.println(request.getName() + " is handle by ConcreteHandler1");
return;
}
if (successor != null) {
successor.handleRequest(request);
}
}
}
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1(null);
Handler handler2 = new ConcreteHandler2(handler1);
Request request1 = new Request(RequestType.TYPE1, "request1");
handler2.handleRequest(request1);
Request request2 = new Request(RequestType.TYPE2, "request2");
handler2.handleRequest(request2);
}
}
> request1 is handle by ConcreteHandler1
request2 is handle by ConcreteHandler2
Process finished with exit code 0
-
优缺点
优点- 对请求的发送者和接收者进行解耦;
- 责任链可以动态的组合;
缺点
- 如果责任链太长或者处理时间太长都会影响性能;
- 责任链有可能过多;
-
案例
javax.servlet.FilterChain#doFilter
命令模式
-
模式动机
将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。 -
适用场景
- 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互;
- 需要抽象出等待执行的行为;
-
类图
-
优缺点
优点:- 较低耦合
- 容易扩折新命令或者一组命令
缺点:
- 命令的无限扩展会增加类的数量,提高系统实现的复杂度;
-
案例
- java.lang.Runnable
- junit.framework.Test#run
策略模式
-
模式动机
定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。策略模式是一种对象行为型模式。 -
适用场景
- 系统有很多类,而他们的区别仅仅在于他们的行为不同;
- 一个系统需要动态地在几种算法中选择一种;
-
类图
-
优缺点
优点- 符合开闭原则;
- 避免使用多重条件转移语句;
- 提高算法的保密性和安全性;
缺点
- 客户端必须知道所有的策略类,并自行选择使用哪一个策略类;
- 会产生很多策略类;
-
案例
解释器模式
-
模式动机
定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。 -
适用场景
某个特定类型问题的发生频率足够高的时候(例如系统中的日志服务); -
类图
- TerminalExpression:终结符表达式,每个终结符都需要一个 TerminalExpression。
- Context:上下文,包含解释器之外的一些全局信息。
-
优缺点
优点:- 语法有很多类表示,容易改变及扩展此"语言"
缺点: - 当语法规则数目太多时,增加了系统的复杂度;
- 语法有很多类表示,容易改变及扩展此"语言"
-
案例
- java.util.Pattern
- java.text.Normalizer
- All subclasses of [java.text.Format]
- javax.el.ELResolver
迭代模式
-
模式动机
提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。 -
适用场景
- 访问一个集合对象的内容而无需暴露它的内部表示;
- 为遍历不同的集合结构提供一个统一的接口;
-
类图
- Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator;
- Iterator 主要定义了 hasNext() 和 next() 方法。
- Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。
public interface Aggregate { Iterator createIterator(); } public class ConcreteAggregate implements Aggregate { private Integer[] items; public ConcreteAggregate() { items = new Integer[10]; for (int i = 0; i < items.length; i++) { items[i] = i; } } @Override public Iterator createIterator() { return new ConcreteIterator<Integer>(items); } } public interface Iterator<Item> { Item next(); boolean hasNext(); } public class ConcreteIterator<Item> implements Iterator { private Item[] items; private int position = 0; public ConcreteIterator(Item[] items) { this.items = items; } @Override public Object next() { return items[position++]; } @Override public boolean hasNext() { return position < items.length; } } public class Client { public static void main(String[] args) { Aggregate aggregate = new ConcreteAggregate(); Iterator<Integer> iterator = aggregate.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
-
优缺点
优点:- 分离了集合对象的遍历行为;
缺点:
- 类的个数成对增加;
-
案例
- java.util.Iterator
- java.util.Enumeration
备忘录模式
-
模式动机
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。 -
适用场景
- 保存及恢复数据相关的业务场景;
- 后悔的时候,即想恢复到之前的状态;
-
类图
- Originator:原始对象
- Caretaker:负责保存好备忘录
- Menento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。
-
优缺点
优点:- 为用户提供一种可恢复的机制;
- 存档信息的封装;
缺点:
- 资源占用
-
案例
java.io.Serializable
状态模式
-
模式动机
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。 -
适用场景
- 一个对象存在多个状态(不同状态下行为不同)且状态可相互切换;
-
类图
-
优缺点
优点- 将不同的状态隔离;
- 把各种状态的转换逻辑,分布到State的子类中,减少相互间的依赖;
- 增加新的状态比较简单;
缺点
- 状态多的业务场景导致类的数目增加,系统将会变得比较复杂;
模板方法模式
-
模式动机
定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 -
适用场景
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;
- 各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复;
-
类图
-
优缺点
优点- 提高复用性;
- 提高扩展性;
- 符合开闭原则;
缺点
- 类数目的增加;
- 增加了系统实现的复杂度;
- 继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都需要改一遍;
-
案例
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
中介者模式
-
模式动机
用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。 -
适用场景
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解;
- 交互的公共行为,如果需要改变行为则可以增加新的中介者类;
-
类图
- Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。
- Colleague:同事,相关对象
Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:
使用中介者模式可以将复杂的依赖结构变成星形结构:
-
优缺点
优点- 将一对多转换成了一对一,降低了程序的复杂度;
- 类之间的解耦;
缺点 - 中介者过多,导致系统复杂;
-
案例
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
观察者模式
-
模式动机
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。 -
适用场景
- 关联行为场景,建立一套触发机制;
-
类图
主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。
观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。
-
优缺点
优点- 观察者与被观察者之间建立了一个抽象的耦合;
- 观察者模式支持广播通信;
缺点
- 观察者之间有过多的细节依赖,提高时间消耗以及程序的复杂度;
- 使用要得当,要避免循环调用;
-
案例
- java.util.Observer
- java.util.EventListener
- javax.servlet.http.HttpSessionBindingListener
访问者模式
-
模式动机
提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。 -
适用场景
- 一个数据结构(如List / Set / Map等)包含很多类型对象;
- 数据结构与数据操作分离;
-
类图
- Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作
- ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
- ObjectStructure:对象结构,可以是组合结构,或者是一个集合。
M公司开发部想要为某企业开发一个OA系统,在该OA系统中包含一个员工信息管理子系统,该企业包括正式员工和临时工,每周HR部门和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等等。该企业的基本制度如下: (1)正式员工(Full time Employee)每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假锁扣工资以80元/小时计算,直到基本工资扣除到0为止。除了记录实际工作时间外,HR部需要记录加班时长或请假时长,作为员工平时表现的一项依据。 (2)临时员工(Part time Employee)每周工作时间不固定,基本工资按照小时计算,不同岗位的临时工小时工资不同。HR部只需要记录实际工作时间。 (3) HR人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,HR人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。public class Client { public static void main(String[] args) { Employee employee1 = new FullTimeEmployee("张三", 45, 3200); Employee employee2 = new FullTimeEmployee("李四", 35, 2000); Employee employee3 = new PartTimeEmployee("王五", 10, 200); Employee employee4 = new PartTimeEmployee("赵六", 29, 180); EmployeeList employeeList = new EmployeeList(); employeeList.add(employee1); employeeList.add(employee2); employeeList.add(employee3); employeeList.add(employee4); Department financeDepartment = new FinanceDepartment(); Department hrDepartment = new HR(); employeeList.accept(financeDepartment); System.out.println("--------------------------------------"); employeeList.accept(hrDepartment); } } > 正式员工:张三,实际工资:3450.0 正式员工:李四,实际工资:1600.0 临时工:王五,实际工资:2000.0 临时工:赵六,实际工资:5220.0 -------------------------------------- 正式工:张三,实际工作:45小时,加班:5小时 正式工:李四,实际工作:35小时,请假:5小时 临时工:王五,实际工作:10小时 临时工:赵六,实际工作:29小时 Process finished with exit code 0
-
优缺点
优点- 增加新的操作将会非常容易,只要增加一个新的访问者就可以了;
缺点
- 增加新的数据结构会比较困难;
- 具体元素的变更会比较麻烦;