设计模式——行为型模式(责任链,命令,解释器,迭代器,中介者,备忘录,观察者,状态,空对象,策略,模板,访问者)
- 2019 年 10 月 23 日
- 笔记
行为型模式是对在不同的对象之间划分责任和算法的抽象化。它不仅关注类和对象的结构,而且重点关注它们之间的相互作用。通过行为型模式,我们可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互
在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行
行为型模式分为两种:
- 类行为型模式:类行为型模式使用继承关系在几个类之间分配行为。该模式主要通过多态等方式来分配父类与子类的职责
- 对象行为型模式:对象行为型模式使用对象的聚合关联关系分配行为。该模式主要通过对象关联等方式来分配两个或多个类的职责
根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式
一、职责链模式
1、楔子
说一个病人看牙的时候,医生不小心把拔掉的牙掉进了病人的嗓子里。各个科室的大夫都推卸责任,搞得病人楼上楼下跑了很多冤枉路,最后也没解决问题
职责链模式就是一种“推卸”责任的模式,你在我这能解决问题,我就给你解决,不行就把你推给其它对象。至于谁最终解决了这个问题,管他呢!
2、解析
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链并沿着这条链传递该请求,直到有一个对象处理它为止
模式组成:
- 抽象处理者角色:定义处理请求的接口。对于链的不同实现,可以在该角色中实现后继链
- 具体处理者角色:实现抽象角色中定义的接口并处理它负责的请求,如果不能处理则访问它的后继者
职责链模式适用场景:
- 有多个对象可以处理请求,具体谁处理运行时自动确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可处理一个请求的对象集合被动态指定
3、举例
有些项目很多地方需要使用代号,比如员工工号、档案代号等,要求客户使用时输入。这些代号对特定的企业或者类别往往有一定的规则。因此可以让用户在系统参数中维护一定的规则,通过“代号自动生成器”为用户生成代号
这里只说明职责链模式的结构和使用,不提现功能细节
//抽象处理者角色 public interface CodeAutoParse { //统一的处理请求使用的接口 String[] generateCode(String moduleCode, int number, String rule,String[] target) throws BaseException; } //具体处理者角色。处理日期 public class DateAutoParse implements CodeAutoParse { //获取当前时间 private final Calendar currentDate = Calendar.getInstance(); //注入下一个处理者,系统中采用 Spring Bean管理 private CodeAutoParse theNextParseOfDate; public void setTheNextParseOfDate(CodeAutoParse theNextParseOfDate){ this.theNextParseOfDate = theNextParseOfDate ; } /* * 实现处理请求的接口 * 该接口首先判断用户定义的格式是否有流水号,有则解析,没有则跳过 * 传到下一个处理者 */ public String[] generateCode(String moduleCode, int number, String rule, String[] target) throws BaseException { ... //这里省略处理的业务 if(theNextParseOfDate != null) return theNextParseOfDate.generateCode(moduleCode , number , rule , target); else return target; } }
二、命令模式
1、楔子
在设计界面时,大家可能注意到这种情况,同样的菜单控件,在不同的应用环境中功能完全不同;而菜单选项的某个功能可能和鼠标右键的某个功能完全一致。按照最原始的设计,这些不同功能的菜单或者右键弹出菜单是要分开实现的,想象一下 word文档上面一排的菜单要实现多少形似神非的菜单类?完全没法搞啊
这是可以运用分离变化与不变的因素,将菜单触发的功能分离出来,制作菜单时只提供一个统一的触发接口。修改设计后,功能点可以被不同的菜单或者右键重用;且菜单控件也能去除变化因素,提高了重用;同时分离了显示逻辑和业务逻辑的耦合
这就是命令模式的雏形
2、解析
命令模式:将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
模式组成:
- 命令角色:声明执行操作的接口。由 Java接口或抽象类实现
- 具体命令角色:将一个接收者对象绑定于一个动作;调用接收者相应方法以实现命令角色声明的执行操作的接口
- 客户角色:创建一个具体命令对象
- 请求者角色:调用命令对象执行该请求
- 接收者角色:知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的
命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。该模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联
命令模式适用场景:
- 需要抽象出待执行的动作,然后以参数形式提供出来
- 在不同的时刻指定、排列和执行请求
- 需要支持取消操作
- 支持修改日志功能
- 需要支持事务操作
3、举例
命令模式在 Web开发中最常见的应用是 Struts中 Action的使用
在 Struts中 Action控制类是整个框架的核心,它连接着页面请求和后台业务逻辑处理。按照框架设计,每一个继承自 Action的子类,都实现 execute方法——调用后台真正处理业务的对象来完成任务
class Action { /* * Action提供了两个版本的接口,实现了默认的空实现 */ public ActionForward execute( ActionMapping mapping, ActionForm form, ServletRequest request, ServletResponse response) throws Exception { try { return execute(mapping, form, (HttpServletRequest) request, (HttpServletResponse) response); } catch (ClassCastException e) { return null; } } public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { return null; } } //请求者角色,调用命令角色执行操作 class RequestProcessor { …… protected ActionForward processActionPerform(HttpServletRequest request, HttpServletResponse response, Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException { try { return (action.execute(mapping, form, request, response)); } catch (Exception e) { return (processException(request, response,e, form, mapping)); } } }
Struts框架为我们提供了以上两个角色,要使用 Struts框架完成自己的业务逻辑,剩下的三个角色就要由我们自己实现:
- 先实现一个 Action的子类,并重写 execute方法。在次方法中调用业务模块的相应对象完成任务
- 实现处理业务的业务类,充当接收者角色
- 配置 struts-config.xml配置文件,将自己的 Action和 Form以及相应页面结合起来
- 编写 jsp,在页面中显示的制定对应的处理 Action
在页面提交请求后,Struts框架会根据配置文件中的定义,将 Action对象作为参数传递给 RequestProcessor类中的 processActionPerform()方法,由此方法调用 Action对象中的执行方法,进而调用业务层中的接收角色。这样就完成了请求的处理
三、解释器模式
1、解析
解释器模式:定义语言的文法,并建立一个解释器来解释该语言中的句子
模式组成:
- 抽象表达角色:声明一个抽象的解释操作,该接口是所有具体表达式角色都要实现的
- 终结符表达式角色:具体表达式
- 实现与文法中的终结符相关联的解释操作
- 句子中的每个终结符需要该类的一个实例与之相呼应
- 非终结符表达式角色:具体表达式
- 文法中的每条规则 R ::= R1R2…Rn 都需要一个非终结符表达式角色
- 从 R1到 Rn的每个符号都维护一个抽象表达角色的实例变量
- 实现解释操作。解释一般要递归的调用表示从 R1到 Rn的那些对象的解释操作
- 上下文角色:包含解释器外的一些全局信息
- 客户角色
- 构建表示该文法定义的语言中的一个特定的句子的抽象文法树
- 调用解释操作
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子
抽象语法树:除了使用文法规则来定义一个语言,在解释器模式中还可以通过一种称之为抽 象语法树的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例
抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符和非终结符类
在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一个语法规则,使得系统具有较好的扩展性和灵活性
解释器模式适用场景:
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释其通过解释这些句子来解决问题。而且当文法简单、效率不是关键的时候效果最好
2、举例
举一个加减乘除的例子
//上下文角色。使用 HashMap存储变量对应的数据 class Context{ private Map valueMap = new HashMap(); public void addValue(Variable x , int y){ Integer yi = new Integer(y); valueMap.put(x , yi); } public int LookupValue(Variable x){ int i = ((Integer)valueMap.get(x)).intValue(); return i ; } } //抽象表达式角色。也可以用接口实现 abstract class Expression{ public abstract int interpret(Context con); } //终结符表达式角色 class Constant extends Expression{ private int i ; public Constant(int i){ this.i = i; } public int interpret(Context con){ return i ; } } class Variable extends Expression{ public int interpret(Context con) { // this为调用 interpret方法的 Variable对象 return con.LookupValue(this); } } //终结符表达式角色 class Add extends Expression{ private Expression left ,right ; public Add(Expression left , Expression right) { this.left = left ; this.right = right ; } public int interpret(Context con){ return left.interpret(con) + right.interpret(con); } } class Subtract extends Expression{ private Expression left , right ; public Subtract(Expression left , Expression right) { this.left = left ; this.right = right ; } public int interpret(Context con){ return left.interpret(con) - right.interpret(con); } } class Multiply extends Expression{ private Expression left , right ; public Multiply(Expression left , Expression right){ this.left = left ; this.right = right ; } public int interpret(Context con){ return left.interpret(con) * right.interpret(con); } } class Division extends Expression{ private Expression left , right ; public Division(Expression left , Expression right){ this.left = left ; this.right = right ; } public int interpret(Context con){ try { return left.interpret(con) / right.interpret(con); } catch(ArithmeticException ae) { System.out.println("㹿䰸᭄Ў 0ʽ"); return -11111; } } } //测试程序,计算 (a*b)/(a-b +2) public class Test{ private static Expression ex ; private static Context con ; public static void main(String[] args){ con = new Context(); //设置变量、常量 Variable a = new Variable(); Variable b = new Variable(); Constant c = new Constant(2); //为变量赋值 con.addValue(a , 5); con.addValue(b , 7); //运算。句子的结构由我们分析、构造 ex = new Division(new Multiply(a , b), new Add(new Subtract(a , b) , c)); System.out.println("䖤ㅫ㒧ᵰЎ˖" + ex.interpret(con)); } }
四、迭代器模式
1、楔子
Java中常用 JDK提供的迭代接口进行 java collection的遍历
Iterator it = list.iterator(); while(it.hasNext()) { //使用 it.next(); 实现一些业务逻辑 }
2、解析
迭代器模式:提供一种方法访问一个容器对象的各个元素,又不需暴露该对象的内部细节
模式组成:
- 迭代器角色:定义访问和遍历元素的接口
- 具体迭代器角色:实现迭代器接口,记录遍历中的当前位置
- 容器角色:提供创建具体迭代器角色的接口
- 具体容器角色:实现创建具体迭代器角色的接口,具体迭代器角色与该容器的结构相关
将遍历对象中数据的行为提取出来封装到一个迭代器中,通过专门的迭代器来遍历对象的内部数据,这就是迭代器模式的本质。迭代器模式是“单一职责原则”的完美体现
迭代器模式适用场景:
- 访问一个容器对象的内容而无需暴露的它的内部表示
- 支持对容器对象的多种遍历
- 为遍历不同的容器结构提供一个统一的接口
3、举例
先列举迭代器模式的实现方式
- 迭代器角色定义了遍历接口,但没有规定由谁控制迭代。客户程序控制遍历进程是外部迭代器,迭代器自身控制迭代是内部迭代器
- 在迭代器模式中没有规定谁实现遍历算法,好像理所当然的在迭代器角色中实现,但这样就破坏了容器的封装
Java collection中,具体迭代器角色定义在容器角色的内部类,这样就保护了容器的封装,同时容器提供了遍历算法接口,可以扩展自己的迭代器
//迭代器角色。定义遍历接口 interface Iterator { boolean hasNext(); Object next(); void remove(); } //容器角色。以 List为例,它仅仅是一个接口,这里略 //具体容器角色。实现了 List接口的 ArrayList等类,为了突出重点,这里只罗列和迭代器相关的内容 //它以内部类的形式显示,AbstractList是为了将各个具体容器角色的公共部分提取出来而存在 public abstract class AbstractList extends AbstractCollection implements List { //负责创建具体迭代器角色的工厂方法 public Iterator iterator() { return new Itr(); } //作为内部类的具体迭代器角色 private class Itr implements Iterator { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public Object next() { checkForComodification(); try { Object next = get(cursor); lastRet = cursor + +; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }
五、中介者模式
1、楔子
对于一个模块,可能由很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式
2、解析
中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
模式组成:
- 抽象中介者角色:定义统一的接口用于各同事角色之间的通信
- 具体中介者角色:通过协调各同事角色来实现协作行为,为此它需要知道并引用各个同事角色
- 同事角色:每个同事角色都知道对应的具体中介者角色,且与其他同事角色通信时一定要通过中介者角色
中介者模式可以使对象之间的关系数量急剧减少
中介者承担两方面的职责:
- 中转作用:各个同事对象不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。中转作用属于中介者在结构上的支持
- 协调作用:中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做。中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。协调作用属于中介者在行为上的支持
中介者模式适用场景:
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解
- 一个对象由于引用了很多其他对象并且直接和这些对象通信,导致难以复用该对象
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类
3、举例
某公司欲开发一套图形界面类库。该类库需要包含若干预定义的窗格(Pane)对象,例如 TextPane、ListPane、GraphicPane等,窗格之间不允许直接引用。基于该类库的应用由一个包含一组窗格的窗口(Window)组成,窗口需要协调窗格之间的行为
//抽象中介者 abstract Mediator { public abstract void mediator(Pane c); } //具体中介者 class Windows extends Mediator { public TextPane textPane; public ListPane listPane; public GraphicPane graphicPane; public void mediator(Pane c) { if (c==textPane){ System.out.println("用户点击了TextPane,--ListPane,GraphicPane--进行显示"); listPane.diaplay(); graphicPane.diaplay(); }else if (c==listPane){ System.out.println("用户点击了ListPane,--TextPane,GraphicPane--进行显示"); textPane.diaplay(); graphicPane.diaplay(); }else { System.out.println("用户点击了GraphicPane,--TextPane,ListPane--进行显示"); textPane.diaplay(); listPane.diaplay(); } } } //抽象组件类 abstract class Pane { private Mediator mediator; public void setMediator(Mediator mediator) { this.mediator = mediator; } public void show(){ this.mediator.mediator(this); } public abstract void diaplay(); } //具体组件类 class TextPane extends Pane{ public void diaplay() { System.out.println("显示TextPane"); } } class GraphicPane extends Pane { public void diaplay() { System.out.println("显示GraphicPane"); } } class ListPane extends Pane{ public void diaplay() { System.out.println("显示ListPane"); } } //测试程序 public class MyClass { public static void main(String[] args){ Windows windows=new Windows(); TextPane textPane=new TextPane(); ListPane listPane=new ListPane(); GraphicPane graphicPane=new GraphicPane(); textPane.setMediator(windows); listPane.setMediator(windows); graphicPane.setMediator(windows); windows.listPane=listPane; windows.textPane=textPane; windows.graphicPane=graphicPane; textPane.show(); System.out.println("——————————————"); graphicPane.show(); } }
六、备忘录模式
1、楔子
电影大话西游给大家带来了极佳的观影体验,其中的“月光宝盒”想必令很多人垂涎,如果世上真有如此宝物,或许真的会少一些伤感与后悔
好了,YY到此结束,现实世界中就不要想 peach了。但是程序中我们却可以回到过去,备忘录模式就是手中的“后悔药”
2、解析
备忘录模式:在不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,以后就能将该对象恢复到原先保存的状态
模式组成:
- 备忘录角色:存储备忘发起角色的内部状态
- 备忘发起角色:创建一个备忘录,记录当前时刻它的内部状态,在需要时使用备忘录恢复内部状态
- 备忘录管理者角色:保存备忘录,但不能操作或检查备忘录的内容
由于在备忘录中存储的是备忘发起角色的中间状态,因此需要防止备忘发起角色以外的其他对象访问备忘录
备忘录对象通常封装了备忘发起角色的部分或所有的状态信息,而且这些状态不能被其他对象访问,也就是说不能在备忘录对象之外保存备忘发起角色的状态,因为暴露其内部状态将违反封装的原则,可能有损系统的可靠性和可扩展性
3、举例
Java中保存备忘录角色封装的方法:
第一种是采用两个不同接口限制访问权限。一个是提供比较完备操作状态的宽接口,一个是作为标志的窄接口。对于备忘发起角色采用宽接口访问,其他角色或对象用窄接口访问。该方法实现简单,但需要人为规范约束
第二种是采用内部类控制访问权限,将备忘录角色作为备忘发起角色的一个私有内部类。下面代码就使用该方法
class Originator{ //要保存的状态 private int state = 90; //ֱ保持一个备忘录管理者角色的对象 private Caretaker c = new Caretaker(); //读取备忘录角色以恢复以前的状态 public void setMemento(){ Memento memento = (Memento)c.getMemento(); state = memento.getState(); System.out.println("the state is " +state + " now"); } //߯创建备忘录角色,并存入当前状态属性,交给备忘录管理者角色存放 public void createMemento(){ c.saveMemento(new Memento(state)); } //this is other business methods... //they maybe modify the attribute state public void modifyState4Test(int m){ state = m; System.out.println("the state is " +state + " now"); } //作为私有内部类的备忘录角色,它实现了窄接口,可以看到在第二种方法中宽接口已不再需要 //注意,里面的属性和方法都是私有的 private class Memento implements MementoIF { private int state ; private Memento(int state){ this.state = state ; } private int getState(){ return state; } } } //测试程序 public class TestInnerClass{ public static void main(String[] args){ Originator o = new Originator(); o.createMemento(); o.modifyState4Test(80); o.setMemento(); } } //窄接口 interface MementoIF { } //备忘录管理者角色 class Caretaker{ private MementoIF m ; public void saveMemento(MementoIF m){ this.m = m; } public MementoIF getMemento(){ return m; } }
七、观察者模式
1、楔子
一个团伙在进行盗窃的时候,总有几个人在门口放风,一旦有什么风吹草动,立即通知里面的同伙撤离。防风的人和里面的人不一定认识,但并不影响通讯,他们已经提前商定好了暗号
放风者好偷窃者之间的关系就是观察者模式的深刻体现
2、解析
观察者模式:定义对象之间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新
模式组成:
- 抽象目标角色:目标角色知道它的观察者,可以有任意多个观察者观察同一个目标,并且提供注册和删除观察者对象的接口。Java中通常由抽象类或接口实现
- 抽象观察者角色:为那些在目标发生改变时需要获得通知的对象定义一个更新接口。Java中通常由抽象类或接口实现
- 具体目标角色:将有关状态存入各个具体目标角色对象,当其状态发生改变时,向它的各个观察者发出通知
- 具体观察者角色:存储有关状态,这些状态应与目标的状态保持一致。实现抽象目标角色的更新接口以使自身状态与目标的状态保持一致。在本角色内可以维护一个指向具体目标角色对象的引用
观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统
该模式的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知
作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知
3、举例
JUnit为用户提供了三种不同的测试结果显示页面,如何让将测试的业务逻辑和显示结果的界面很好的分离开就是观察者模式的运用
//抽象观察者角色。JUnit采用接口实现 //定义了4个不同的 update方法,对应4种不同的状态变化 interface TestListener { /** * An error occurred. */ public void addError(Test test, Throwable t); /** * A failure occurred. */ public void addFailure(Test test, AssertionFailedError t); /** * A test ended. */ public void endTest(Test test); /** * A test started. */ public void startTest(Test test); } //具体观察者角色。使用 TextUI说明 class ResultPrinter implements TestListener { //省略无关代码 //实现接口 TestListener的4个方法 /** * @see junit.framework.TestListener# addError(Test, Throwable) */ public void addError(Test test, Throwable t) { getWriter().print("E"); } /** * @see junit.framework.TestListener# addFailure(Test, AssertionFailedError) */ public void addFailure(Test test, AssertionFailedError t) { getWriter().print("F"); } /** * @see junit.framework.TestListener# endTest(Test) */ public void endTest(Test test) { } /** * @see junit.framework.TestListener#startTest(Test) */ public void startTest(Test test) { getWriter().print("."); if (fColumn + + > = 40) { getWriter().println(); fColumn = 0; } } } //目标角色。仅列出 Failures发生时如何通知观察者 public class TestResult extends Object { //存放测试 Failures的集合 protected Vector fFailures; //存放注册的观察者的集合 protected Vector fListeners; public TestResult() { fFailures= new Vector(); fListeners= new Vector(); } /** * Adds a failure to the list of failures. The passed in exception * caused the failure. */ public synchronized void addFailure(Test test, AssertionFailedError t) { fFailures.addElement(new TestFailure(test, t)); //通知各个观察者的 addFailure方法 for (Enumeration e = cloneListeners().elements(); e.hasMoreElements(); ) { ((TestListener)e.nextElement()).addFailure(test, t); } } /** * 注册一个观察者 */ public synchronized void addListener(TestListener listener) { fListeners.addElement(listener); } /** * 删除一个观察者 */ public synchronized void removeListener(TestListener listener) { fListeners.removeElement(listener); } /** * 返回一个观察者集合的拷贝,防止对观察者集合的非法操作 * 所有使用观察者集合的地方都通过它 */ private synchronized Vector cloneListeners() { return (Vector)fListeners.clone(); } ... } //j建立各角色间的联系,JUnit中通过 TestRunner实现 class TestRunner extends BaseTestRunner { private ResultPrinter fPrinter; public TestResult doRun(Test suite, boolean wait) { //在这里注册 result.addListener(fPrinter); ... } }
八、状态模式
1、解析
状态模式允许一个对象在其内部状态改变时改变它的行为
模式组成:
- 使用环境角色:客户程序通过它满足自己的需求。定义了客户程序需要的接口,并且维护一个具体状态角色的实例,这个实例决定当前的状态
- 状态角色:定义一个接口来封装与使用环境角色某个特定状态相关的行为
- 具体状态角色:实现状态角色定义的接口
状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。模式的关键是引入一个抽象类来专门表示对象的状态,对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换
优点:
- 封装了转换规则
- 枚举可能的状态,在枚举状态之前需要确定状态种类
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点:
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码
状态模式适用场景:
- 行为随状态改变而改变的场景
- 条件、分支语句的代替者
2、举例
创建一个 State接口和实现了 State接口的实体状态类。Context是一个带有某个状态的类。然后使用 Context和状态对象来演示 Context 在状态改变时的行为变化
//创建一个接口 interface State { public void doAction(Context context); } //创建实现接口的实体类 class StartState implements State { public void doAction(Context context) { System.out.println("Player is in start state"); context.setState(this); } public String toString(){ return "Start State"; } } class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } public String toString(){ return "Stop State"; } } //创建 Context类 class Context { private State state; public Context(){ state = null; } public void setState(State state){ this.state = state; } public State getState(){ return state; } } //使用 Context查看当状态 State改变时的行为变化 public class StatePatternDemo { public static void main(String[] args) { Context context = new Context(); StartState startState = new StartState(); startState.doAction(context); System.out.println(context.getState().toString()); StopState stopState = new StopState(); stopState.doAction(context); System.out.println(context.getState().toString()); } }
九、空对象模式
在空对象模式中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为
在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方
然后举例说明:在一个图书信息查询系统中调用一个方法,传过去要查找图书的ID,然后它返回要查找的图书对象,这样就可以调用对象的方法来输出图书的信息
//提供构造函数和展示图书信息的show()方法 class ConcreteBook { private int ID; private String name; private String author; // 构造函数 public ConcreteBook(int ID, String name, String author) { this.ID = ID; this.name = name; this.author = author; } public void show() { System.out.println(ID + "**" + name + "**" + author); } } //创建图书对象的图书工厂,主要提供一个获得ConcreteBook的方法 class BookFactory { public ConcreteBook getBook(int ID) { ConcreteBook book = null; switch (ID) { case 1: book = new ConcreteBook(ID, "设计模式", "GoF"); break; case 2: book = new ConcreteBook(ID, "被遗忘的设计模式", "Null Object Pattern"); break; default: book = null;// 其实这个可以省略,因为初始化已经赋值为null。 break; } return book; } } //测试代码 public class Client { static void main(String[] args) { BookFactory bookFactory = new BookFactory(); ConcreteBook book = bookFactory.getBook(1); book.show(); } }
十、策略模式
1、楔子
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务
2、解析
策略模式定义一系列的算法,八折些算法挨个封装成拥有共同接口的单独的类,并且使它们之间可以互换
模式组成:
- 算法使用环境角色:算法被引用到这里和其他与环境有关的操作一起完成任务
- 抽象策略角色:规定所有具体策略角色需要的接口。Java中通常由抽象类或接口实现
- 具体策略角色:实现抽象策略角色定义的接口
策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类
在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。策略模式仅仅封装算法,提供新算法插入到已有系统以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定
3、举例
以局部管理器为例。在 java.awt类库中有很多设定好的 Container对象的布局格式,这些格式可以在创建软件界面时用到。如果不使用策略模式就没有了对布局格式扩展的可能,因为修改 Container中的方法以让它知道布局格式显然不可行
Container中有关代码
LayoutManager layoutMgr; //对布局管理器接口的引用 //获得在使用的具体布局管理器 public LayoutManager getLayout() { return layoutMgr; } //设置要使用的具体布局管理器 public void setLayout(LayoutManager mgr) { layoutMgr = mgr; if (valid) { invalidate(); } }
可以看到 Container不关心使用的是什么具体布局管理器,使得 Container不会随着局部管理器的增多而修改本身,所以说策略模式是对变化的封装
布局管理器接口代码
public interface LayoutManager { void addLayoutComponent(String name, Component comp); ... Dimension minimumLayoutSize(Container parent); void layoutContainer(Container parent); }
具体的布局管理器就是对上面接口方法的实现和扩展,这里不再显示。下面是对它的使用
public class FlowLayoutWindow extends JApplet { public void init() { Containter cp = getContentPane(); cp.setLayout(new FlowLayout()); for(int i = 0 ; i < 20 ; i+ +) cp.add(new JButton("Button" + i)); } ... }
十一、模板模式
1、解析
模板模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
模式组成:
- 抽象类:定义一到多个抽象方法,以供具体的子类实现它们,还要实现一个模板方法定义算法的骨架。该模板方法不仅调用前面的抽象方法,也调用其他操作,只要能完成使命
- 具体类:实现父类中的抽象方法以完成算法中与特定子类相关的步骤
使用模板方法可以使系统扩展性增强,最小化变化对系统的影响
2、举例
// 步骤 1 // 创建一个抽象类,它的模板方法被设置为 final。 // Game.java abstract class Game { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); //模板 public final void play(){ //初始化游戏 initialize(); //开始游戏 startPlay(); //结束游戏 endPlay(); } } // 步骤 2 // 创建扩展了上述类的实体类。 // Cricket.java class Cricket extends Game { void endPlay() { System.out.println("Cricket Game Finished!"); } void initialize() { System.out.println("Cricket Game Initialized! Start playing."); } void startPlay() { System.out.println("Cricket Game Started. Enjoy the game!"); } } // Football.java class Football extends Game { void endPlay() { System.out.println("Football Game Finished!"); } void initialize() { System.out.println("Football Game Initialized! Start playing."); } void startPlay() { System.out.println("Football Game Started. Enjoy the game!"); } } // 步骤 3 // 使用 Game 的模板方法 play() 来演示游戏的定义方式。 // TemplatePatternDemo.java public class TemplatePatternDemo { public static void main(String[] args) { Game game = new Cricket(); game.play(); System.out.println(); game = new Football(); game.play(); } }
十二、访问者模式
1、楔子
对于系统中的某些对象,它们存储在同一个集合中,且具有不同的类型。对于该集合中的对象,可以接受一类称为访问者的对象来访问,而且不同的访问者其访问方式有所不同
在实际使用时,对同一集合对象的操作并不是唯一的,对相同的元素对象可能存在多种不同的操作方式。而且这些操作方式并不稳定,可能还需要增加新的操作,以满足新的业务需求
此时访问者模式就是一个值得考虑的解决方案
2、解析
访问者模式表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作
模式组成:
- 访问者角色:为对象结构中具体元素角色声明一个访问操作的接口。操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素就是的特定接口直接访问它
- 具体访问者角色:实现每个访问者角色声明的操作
- 元素角色:定义 Accept操作,以访问者为参数
- 具体元素角色:实现由元素角色提供的 Accept操作
- 对象结构角色:模式必备角色。具有如下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或一个集合,如一个列表或一个无序集合
访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问
访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者;一个是元素层次结构,提供了抽象元素和具体元素
相同的访问者能以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性
3、举例
创建一个定义接收操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是实现了 ComputerPart 接口的实体类。再定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作
演示类使用 Computer、ComputerPartVisitor 类来演示访问者模式的用法
//定义一个表示元素的接口 interface ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor); } //创建扩展了上述类的实体类 class Keyboard implements ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Monitor implements ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Mouse implements ComputerPart { public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } } class Computer implements ComputerPart { ComputerPart[] parts; public Computer(){ parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()}; } public void accept(ComputerPartVisitor computerPartVisitor) { for (int i = 0; i < parts.length; i++) { parts[i].accept(computerPartVisitor); } computerPartVisitor.visit(this); } } //定义一个表示访问者的接口 interface ComputerPartVisitor { public void visit(Computer computer); public void visit(Mouse mouse); public void visit(Keyboard keyboard); public void visit(Monitor monitor); } //创建实现了上述类的实体访问者 class ComputerPartDisplayVisitor implements ComputerPartVisitor { public void visit(Computer computer) { System.out.println("Displaying Computer."); } public void visit(Mouse mouse) { System.out.println("Displaying Mouse."); } public void visit(Keyboard keyboard) { System.out.println("Displaying Keyboard."); } public void visit(Monitor monitor) { System.out.println("Displaying Monitor."); } } //使用 ComputerPartDisplayVisitor显示 Computer的组成部分 public class VisitorPatternDemo { public static void main(String[] args) { ComputerPart computer = new Computer(); computer.accept(new ComputerPartDisplayVisitor()); } }