第25次文章:行为型模式
- 2019 年 10 月 8 日
- 笔记
本周以及上周的学习,主要将11种行为型模式全部结束。来个小总结哈!
行为型模式
关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共有11种模式。
行为型模式汇总 |
|
---|---|
(1)责任链模式 |
chain of responsibility |
(2)命令模式 |
command |
(3)解释器模式 |
interpreter |
(4)迭代器模式 |
iterator |
(5)中介者模式 |
mediator |
(6)备忘录模式 |
memento |
(7)观察者模式 |
observer |
(8)状态模式 |
state |
(9)策略模式 |
strategy |
(10)模板方法模式 |
template method |
(11)访问者模式 |
visitor |
一、责任链模式(chain of responsibility)
1、定义
将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能,则处理,如果不能则传递给链上的下一个对象。
2、代码实现
在日常生活中,我们经常面临着请假等等问题。面对不一样的请假时间,我们需要有不同的管理者来进行处理。比如请假3天以内,由主任处理;10天以内,由经理处理;30天以内,由总经理处理。由此便形成了一个责任链模式,当遇到雇员的请假请求时,从主任开始,层层向上传递,直到最后问题被解决。对于主任,经理和总经理,我们可以统一建立一个Leader抽象类,使得各位管理者统一继承该抽象类,此抽象类中提供一个处理请求的方法。
(1)首先创建一个请假的请求模板
public class Leave { private String empName;//请假的雇员的名字 private int leaveDays;//请假天数 private String response;//请假理由 public Leave(String empName, int leaveDays, String response) { super(); this.empName = empName; this.leaveDays = leaveDays; this.response = response; } public String getEmpName() { return empName; } public int getLeaveDays() { return leaveDays; } public String getResponse() { return response; }}
(2)创建一个Leader抽象类,提供一个抽象处理请求的方法
public abstract class Leader { private String name; private Leader nextLeader; public Leader(String name) { super(); this.name = name; } public Leader getNextLeader() { return nextLeader; } public void setNextLeader(Leader nextLeader) { this.nextLeader = nextLeader; } public String getName() { return name; } //处理请求的方法 public abstract void handleRequest(Leave leave);}
(3)依次创建相关的领导类,并实现相关的方法
//主任类public class Director extends Leader{ public Director(String name) { super(name); } @Override public void handleRequest(Leave leave) { if(leave.getLeaveDays()<3) { System.out.println("请假人:"+leave.getEmpName()+"t请假时间:"+leave.getLeaveDays()+ "天t请假原因:"+leave.getResponse()); System.out.println("审批人:主任"+this.getName()+"t审批意见:同意请假!"); }else { if(this.getNextLeader()!=null) { this.getNextLeader().handleRequest(leave); } } }}
//经理类public class Manager extends Leader{ public Manager(String name) { super(name); } @Override public void handleRequest(Leave leave) { if(leave.getLeaveDays()<10) { System.out.println("请假人:"+leave.getEmpName()+"t请假时间:"+leave.getLeaveDays()+ "天t请假原因:"+leave.getResponse()); System.out.println("审批人:经理"+this.getName()+"t审批意见:同意请假!"); }else { if(this.getNextLeader()!=null) { this.getNextLeader().handleRequest(leave); } } }}
//总经理类public class SuperiorManager extends Leader{ public SuperiorManager(String name) { super(name); } @Override public void handleRequest(Leave leave) { if(leave.getLeaveDays()<30) { System.out.println("请假人:"+leave.getEmpName()+"t请假时间:"+leave.getLeaveDays()+ "天t请假原因:"+leave.getResponse()); System.out.println("审批人:总经理"+this.getName()+"t审批意见:同意请假!"); }else { System.out.println("此人想要辞职,竟然请假"+leave.getLeaveDays()+"天!"); } }}
(4)测试代码
public class Client { public static void main(String[] args) { Director a1 = new Director("张三"); Manager a2 = new Manager("李四"); SuperiorManager a3 = new SuperiorManager("王五"); //设置优先级 a1.setNextLeader(a2); a2.setNextLeader(a3); Leave leave = new Leave("赵六",46,"回家探亲"); Leave leave1 = new Leave("赵六",1,"回家探亲"); Leave leave2 = new Leave("赵六",5,"回家探亲"); Leave leave3 = new Leave("赵六",25,"回家探亲"); a1.handleRequest(leave); a2.handleRequest(leave1); a1.handleRequest(leave2); a1.handleRequest(leave3); }}
查看结果图:

tips:
(1)在责任链模式中,我们首先需要自己创建每一个领导类的对象,按照我们的业务逻辑设置相应的优先级。
(2)在选择第一个处理者时,如果当前处理者无法处理相应的请求,那么将会直接将此请求传递给后面的管理者进行处理。但是也可以直接选择最高级的管理者(SuperiorManager)作为第一个处理者,如红色框中所示:我们对于请假5天的员工,也可以直接将其递交给总经理进行处理。
3、开发中常见的场景
(1)java中,异常机制就是一种责任链模式。一个try可以对应多个catch,当第一个catch不匹配类型,则自动跳到第二catch。
(2)JavaScript语言中,事件的冒泡和捕获机制。java语言中,事件的处理采用观察者模式。
(3)servlet开发中,过滤器的链式处理。
(4)Struts2中,拦截器的调用也是典型的责任链模式。
4、总结
对于责任链模式,其实完全可以使用ifelse语句进行判断处理,但是之所以还是推崇责任链模式,还是因为程序的扩展性问题。如果我们在后续的维护中,需要再增加一个处理10天——20天请求的副总经理,那么在ifelse条件下,我们将需要在源代码的基础上更改ifelse逻辑。然而使用责任链模式的话,我们就只需要再增加一个副总经理类,然后在设置相关优先级的时候直接将副总经理设置为经理的nextleader就可以了,这样就大大提高了整个程序后期的维护效率!
二、迭代器模式(iterator)
1、场景
(1)提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
(2)聚合对象:存储数据
(3)迭代器:遍历数据
2、代码实现
这里并没有具体的现实场景提供,迭代器在讲解容器的时候,已经有所提到,这里更注重的是我们自己来实现迭代器的底层原理。
(1)创建一个迭代器接口,规定几种特定的方法。
public interface MyIterator { void first(); //将游标指向第一个元素 void next(); //将游标指向下一个元素 boolean hasNext(); //判断是否还有下一个元素 boolean isFirst(); //是否是第一个元素 boolean isLast(); //是否是最后一个元素 Object getCurrentObject(); //获取当前游标指向的对象}
tips: 这里主要是提供几个常见的方法,需要后面的具体的迭代器加以实现其内部方法。
(2)自定义一个聚合类
public class ConcreteMyAggregate { List<Object> list = new ArrayList<Object>(); public void add(Object object) { this.list.add(object); } public void remove(Object object) { this.list.remove(object); } public Object getObject(int index) { return this.list.get(index); } public int getSize() { return this.list.size(); } public List<Object> getList() { return list; } public void setList(List<Object> list) { this.list = list; } //获得迭代器 public ConcreteIterator creteIterator() { return new ConcreteIterator(); } //使用内部类定义迭代器,可以直接使用外部类的属性 private class ConcreteIterator implements MyIterator{ private int cursor=0; @Override public void first() { cursor =0; } @Override public void next() { if (cursor < list.size()) { cursor++; } } @Override public boolean hasNext() { return cursor == (list.size())?false:true ; } @Override public boolean isFirst() { return cursor==0; } @Override public boolean isLast() { return cursor==(list.size()-1); } @Override public Object getCurrentObject() { return list.get(cursor); } }}
tips:
1.在具体的自定义聚合类中,主要是对存储聚合类的容器list进行增删改操作;
2.与此同时,我们定义了一个迭代器内部类,实现迭代器MyIterator接口。将迭代器定义为内部类,主要是为了方便调用内部的list属性,可以直接使用内部类属性cursor作为索引,对外部类的属性list进行操作。这样极大的方便了迭代器在编写过程。
(3)测试代码
public class Client { public static void main(String[] args) { ConcreteMyAggregate cma = new ConcreteMyAggregate(); cma.add("aaa"); cma.add("bbb"); cma.add("ccc"); cma.add("sss"); System.out.println(cma.getSize()); System.out.println(cma.getObject(0)); MyIterator iterator = cma.creteIterator(); while(iterator.hasNext()) { System.out.println(iterator.getCurrentObject()); iterator.next(); } }}
结果图:

tips:在整个迭代器模式中,我们最主要的是体会迭代器的底层实现思想。关注每一个游标指向不同索引时,代表的是容器中的哪一个位置。
三、中介者模式(mediator)
1、核心
(1)如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象称为“同事对象”。
(2)我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构化解为星型结构。
2、中介者模式的本质
解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系。
3、代码实现
在一个公司中,总会有许多部门,部门之间一般不会直接交流,而是通过一个总经理(或其他身份的领导者)进行相关的沟通,然后总经理负责各个部门之间的协调发展。这里我们以研发部,市场部以及财务部为例。
(1)部门接口和每个部门的实现
// 部门类,同事对象接口public interface Department { void selfAction(); //部门自己的事情 void outAction(); //部门以外的事情}
// 研发部(同事对象)public class Development implements Department{ private Mediator m;//持有中介者的引用 public Development(Mediator m) { super(); this.m = m; m.register("研发部", this); //将同事对象注册在其中 } @Override public void selfAction() { System.out.println("专心科研,生产新产品!"); } @Override public void outAction() { System.out.println("缺钱,请求财政部资金支持!"); m.command("财政部"); }}
//财政部(同事对象)public class Financial implements Department{ private Mediator m;//持有中介者的引用 public Financial(Mediator m) { super(); this.m = m; m.register("财政部", this); //将同事对象注册在其中 } @Override public void selfAction() { System.out.println("数钱!"); } @Override public void outAction() { System.out.println("钱太多,花不完!"); m.command("财政部"); }}
// 市场部(同事对象)public class Market implements Department{ private Mediator m;//持有中介者的引用 public Market(Mediator m) { super(); this.m = m; m.register("市场部", this); //将同事对象注册在其中 } @Override public void selfAction() { System.out.println("跑市场,接业务!"); } @Override public void outAction() { System.out.println("缺钱,请求财政部资金支持!"); m.command("财政部"); }}
tips:在每一个同事类对象中,分别向外提供一个展示自己的内部工作的方法,和一个向外部请求的方法。此处可以根据需要设计的很复杂,这里仅仅是做一个简单的假设,使用简单的逻辑来说明中介者模式的内容。
(2)中介者的接口以及相关实现
public interface Mediator { void register(String dname,Department department); void command(String dname);}
// 总经理类(中介者对象)public class President implements Mediator{ Map<String,Department> map = new HashMap<String,Department>();//注册所有的部门 @Override public void register(String dname, Department department) { map.put(dname, department); } /** * 在总经理类的命令方法中,可以根据需求,传入不同的参数,以实现复杂的功能 * 此处仅为简单的介绍,不做深入 */ @Override public void command(String dname) { map.get(dname).selfAction(); } }
tips:在中介者中,我们提供一个map进行存放所有的部门,也属于对每个部门的注册,
(3)测试代码
public class Client { public static void main(String[] args) { President p = new President(); Market market = new Market(p); Development development = new Development(p); Financial financial = new Financial(p); market.selfAction(); System.out.println("###################"); market.outAction(); }}
结果图:

tips:
1.在测试代码中,我们主要使用了“市场部“和“财政部“作为一个相互调用的关系进行测试。
2.在测试代码中,我们创建了一个中介者对象p,在整个代码的运行中,我们并没有直接使用此中介对象,仅仅是作为一个参数属性传递给了每一个同事对象。通过源码我们也可以看出来,中介者主要是在各个同时对象的内部发挥作用。在每个同事对象的外部方法声明中,我们还可以增加ifelse语句,来完成更加复杂的请求。这些就属于中介者模式的优化!
4、开发中常见的场景
(1)MVC模式(其中的C,控制器就是一个中介者对象。M和V都和他打交道)
(2)窗口游戏程序,窗口软件开发中窗口对象也是一个中介者对象
(3)图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者对象来解决,可以是整体的窗口对象或者DOM对象。
四、命令模式(command)
1、介绍
将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。也称之为:动作Action模式、事务transaction模式
2、结构
(1)Command抽象命令类
(2)ConcreteCommand具体命令类
(3)Invoker调用者/请求者:请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接受者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接受者的相关操作。
(4)Receiver接受者
-接受者执行与请求相关的操作,具体实现对请求的业务处理。
-未抽象前,实际执行操作内容的对象。
(5)Client客户类
在客户类中需要创建调用者对象、具体命令类对象,在创建具体命令对象时指定对应的接受者。发送者和接收者之间没有直接关系,都通过命令对象间接调用。
3、代码实现
按照上面的结构,我们依次实现相应的类型
(1)创建Receiver,作为真正命令的执行者
public class Receiver { public void action() { System.out.println("receiver action!"); }}
(2)对命令类的抽象和具体实现
public interface Command { /** * 这个方法是一个返回值为空的方法。 * 实际项目中,可以根据需求设计多个不同的方法 */ void execute();} class ConcreteCommand implements Command{ private Receiver receiver ; public ConcreteCommand(Receiver receiver) { super(); this.receiver = receiver; } @Override public void execute() { //在执行命令前或后可以执行多条命令 receiver.action(); }}
(3)命令的调用者和发起者
//调用者、发起者public class Invoke { private Command command; //也可以通过容器List<Command>容纳很多命令,数据库底层的事务管理就是类似的结构! public Invoke(Command command) { super(); this.command = command; } //业务方法,用于调用命令类的方法 public void call() { command.execute(); }}
(4)测试代码
public class Client { public static void main(String[] args) { Command command = new ConcreteCommand(new Receiver()); Invoke invoker = new Invoke(command); invoker.call(); //以上方法与下列相同 new Receiver().action(); }}
结果图:

tips:
1.在测试代码中,我们写了两个调用receiver.action的方法。第一种是使用的命令模式,第二种是直接通过new对象的方式进行。
2.直观来看,第二种方式更加简洁。如果一个程序能够一直保持不变,那么我们很多设计模式都是没有必要使用的。在使用设计模式的时候,大多数情况都是为整个程序的后期维护和扩展而考虑的。所以,将上述的两种方法进行简单的对比之后,我们可以发现,使用第一种命令模式的优势在于,我们可以在receiver.action方法执行前后,添加许多自己的方法与功能,比如将此命令记入日志,命令的撤销和恢复等等。这些才属于我们设计模式的优势,而不是简单的看一条命令的运行简洁与否。
4、开发中常见的场景
(1)Struts2中,action的整个调用过程中就有命令模式
(2)数据库事务机制的底层实现
(3)命令的撤销和恢复
五、解释器模式(Interpreter)
1,介绍
(1)是一种不常用的设计模式。
(2)用于描述如何构成一个简单的语言解释器,主要用于面向对象语言开发的编译器和解释器设计。
(3)当我们需要开发一种新的语言时,可以考虑使用解释器模式。
(4)尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby,Groovy,java的Js引擎来代替解释器的作用,弥补java语言的不足。
2、开发中常见的场景
(1)EL表达式的处理
(2)正则表达式计时器
(3)SQL语法的解释器
(4)数学表达式解析器
3、补充
解释器模式的使用,更多的在于重新编译一门新的语言,难以找到对应的场景进行实例化,所以此处不对其进行代码实现。后期假如使用到此模式,可以再重新学习编写。
六、访问者模式(Visitor)
1、模式动机
对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式也有所不同。
2、定义
表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素类的前提下定义作用于这些元素的新操作。
3、开发中的场景(应用范围非常窄,了解即可)
(1)XML文档解析器设计
(2)编译器的设计
(3)复杂对象的处理
七、策略模式(strategy)
1、场景
某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略很复杂,可以简单作如下分类:
(1)普通客户小批量报价
(2)普通客户大批量报价
(3)老客户小批量报价
(4)老客户大批量报价
假如我们不使用任何模式,直接使用ifelse语句来完成,也是可以达到基本的要求,那么代码将会是下面的逻辑:
public class TestStrategy { public double getPrice(String type,double price) { if(type.equals("普通客户小批量")) { System.out.println("不打折,原价出售"); return price; }else if(type.equals("普通客户大批量")) { System.out.println("打九折"); return price*0.9; }else if(type.equals("老客户小批量")) { System.out.println("打八五折"); return price*0.85; }else if(type.equals("老客户大批量")) { System.out.println("打八折"); return price*0.8; } return price; }}
tips:我们来对其进行简单的分析。
- 实现起来比较简单,符合一般开发人员的思路。
- 假如类型特别多,算法比较复杂时,整个条件语句的代码就变得很长,难于维护。
- 如果有新增类型,就需要频繁的修改此处的代码!
- 不符合开闭原则。
所以针对上面的这些问题:具体选用哪个报价策略,这需要根据实际情况来确定。这时候,我们采用策略模式即可。
2、核心
策略模式对应于解决某一个问题的算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。
3、代码实现
(1)构建一个策略接口,并实现相应的策略类
public interface Strategy { public double getPrice(double standardPrice);}
public class NewCustomerFewStrategy implements Strategy{ @Override public double getPrice(double standardPrice) { System.out.println("不打折,原价出售"); return standardPrice; }}
public class NewCustomerManyStrategy implements Strategy{ @Override public double getPrice(double standardPrice) { System.out.println("打九折"); return standardPrice*0.9; }}
public class OldCustomerFewStrategy implements Strategy{ @Override public double getPrice(double standardPrice) { System.out.println("打八五折"); return standardPrice*0.85; }}
public class OldCustomerManyStrategy implements Strategy{ @Override public double getPrice(double standardPrice) { System.out.println("打八折"); return standardPrice*0.8; }}
(2)构建一个环境变量,用于客户端和各个算法之间的交互
public class Context { private Strategy strategy; //当前采用的算法对象 //可以通过构造器来注入 public Context(Strategy strategy) { super(); this.strategy = strategy; } //可以通过set方法来注入 public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void computePrice(double price) { System.out.println("您的报价为:"+strategy.getPrice(price)); }}
tips:Context类主要负责和具体的策略类交互,这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化, 如果使用spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法。
(3)测试代码
public class Client { public static void main(String[] args) { Context context = new Context(new OldCustomerFewStrategy()); context.computePrice(1000); context.computePrice(800); context.setStrategy(new OldCustomerManyStrategy()); context.computePrice(1000); context.setStrategy(new NewCustomerManyStrategy()); context.computePrice(1000); context.setStrategy(new NewCustomerFewStrategy()); context.computePrice(1000); }}
结果图:

tips :对比之前的ifelse语句,策略模式的整体代码更加复杂,但是策略模式的优势就在于整个代码的扩展性强。假如在上述场景中,如果需要再增加一个新的策略,那么我们可以直接增加相应的策略类即可,并不要更改任何源代码。
4、本质
分离算法,选择实现。
八、模板方法模式(template method)
1、介绍
模板方法模式是编程中经常用到的模式,它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
2、核心
处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用模板方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤父类中定义好,具体实现延迟到子类中定义。
3、方法回调(钩子方法)
子类不能调用父类,而通过父类调用子类。这些调用步骤已经在父类中写好了,完全由父类控制整个过程。
4、代码实现
比如在银行办理业务过程中,我们都知道具体的流程就是:排队取号、进行交易、服务打分。整个流程是清晰的,但是具体到“交易”步骤的时候,对于每个顾客而言,这是不清楚的。所以我们需要对“交易”步骤进行具体的实现。
(1)创建一个方法流程模板
/** * 定义一个模板方法 */public abstract class BankTempleteMethod { public void line() { System.out.println("排队取号!"); } public abstract void transcet(); public void pp(){ System.out.println("对服务进行打分"); } /** * 此处属于模板方法,整个架构固定不动 * 所以使用final进行修饰,不允许子类修改 */ public final void process() { this.line(); this.transcet(); this.pp(); }}
tips:由于交易的类型不确定,但是又属于一定需要实现的一个方法,所以我们将整个方法模板设置为抽象类,在子类中,具体实现这个交易的细节。
(2)具体实现
public class Client { public static void main(String[] args) { BankTempleteMethod btm = new BankTempleteMethod() { @Override public void transcet() { System.out.println("存钱!!!"); } }; btm.process(); BankTempleteMethod btm2 = new BankTempleteMethod() { @Override public void transcet() { System.out.println("取钱!!!"); } }; btm2.process(); }}
结果图:

tips:在我们具体实现抽象方法的时候,由于每个具体对象的抽象方法一般都是不同的,所以我们一般都是使用匿名内部类的方法完成抽象方法的具体实现。
5、用到模板方法模式的场景
实现一个算法时,整体步骤很固定。但是,某些部分易变。易变部分可以抽象出来,供子类实现。
九、状态模式(state)
1、核心
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
2、结构
(1)Context环境类:环境类中维护一个State对象,他是定义了当前的状态
(2)State抽象状态类
(3)ConcreteState具体状态类:每一个类封装了一个状态对应的行为
3、代码实现
在状态模式中,我们主要是对一个对象的不同状态做出不同的响应即可,所以我们需要分析清楚对象的所有状态,然后进行封装即可。我们将酒店房间作为对象,假定分别有预定,入住、空闲,三种状态,然后进行实现即可。
(1)定义每一种状态,先构建一个状态接口,然后对所有状态加以实现此接口
public interface State { //提供一个处理方法 public void handle();}
/** * 预定状态 */public class BookState implements State { @Override public void handle() { System.out.println("房间处于已经被预定的状态,等待入住!"); }}
/** * 已经入住的状态 */public class CheckedInState implements State{ @Override public void handle() { System.out.println("房间已经入住,请勿打扰!"); }}
/** * 房间属于空闲状态 */public class FreeState implements State { @Override public void handle() { System.out.println("房间还没有被预定,处于空闲状态,现在可以预定!"); }}
(2)对环境变量Context的构建
/** * 上下文文本,类似于房间类,主要用来代表当前状态 */public class Context { private State s ; public void setS(State s) { System.out.println("状态修改中!"); this.s = s; s.handle(); }}
tips:对于上面的环境变量,我们可以将其理解为房间类,一个Context类就代表着一个房间,然后通过setState()方法,更改房间的状态,通过传递不同的state,更改私有属性State,然后完成对象的不同状态。上述代码中的类图关系如下:

(3)测试代码
public class Client { public static void main(String[] args) { Context context = new Context(); context.setS(new BookState()); context.setS(new FreeState()); context.setS(new CheckedInState()); }}
结果图:

tips:通过实现的代码可以看出,对于不同的状态,我们都是单独封装成类,然后通过传递给上下文context(相当于是房间对象)进行上下调用的。
十、观察者模式(Observer)
1、核心
(1)观察者模式主要用于1:N的通知。当一个对象(目标对象Subject或Objservable)的状态变化时,他需要及时告知一系列对象(观察者对象,Observer),令他们做出响应
(2)通知观察者的方式:
-推:每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接受。
-拉:观察者只要知道有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。
2、代码实现
与其他模式相同,分析模式的需求之后,我们首先可以确认的就是有两个对象,一个是目标对象subject和另一个对象observer。在每一个对象的建立之前,我们需要依次建立响应的接口,以便于后期整个代码的扩展。
(1)首先创建一个观察者接口,以及实现此接口的具体观察者类
public interface Observer { public void update(Subject subject); }
public class ObserverA implements Observer { private int myState; @Override public void update(Subject subject) { this.myState = ((ConcreteSubject)subject).getState(); } public int getMyState() { return myState; }}
tips:在观察者接口中,我们只提供一个必须实现的update()方法,可以有多个不同的观察者类实现该接口,并且在不同的观察者类中,可以根据自己的需求增加不同的方法。此处我们为了简洁介绍,仅仅实现了一个观察者类ObserverA,额外增加了一个getMyState的方法,便于外部调用。
(2)创建一个广播者类,以及继承抽闲广播类的具体广播者
public class Subject { private List<Observer> list = new ArrayList<Observer>(); public void registorObserver(Observer obs) { list.add(obs); } public void removeObserver(Observer obs) { list.remove(obs); } public void notifyAllObserver() { for (Observer observer : list) { observer.update(this); } }}
public class ConcreteSubject extends Subject{ private int state; public int getState() { return state; } public void setState(int state) { this.state = state; //主题对象(目标对象)的值发生了变化,请通知所有的观察者 this.notifyAllObserver(); }}
tips:在目标对象的创建中,由于每个目标对象都会有大量相同的对象,所以我们选择构建一个类的方法,将所有重复的代码方法放在父类中,在子类中增加自己所需要的方法即可。经过上述代码的创建类对象创建之后,可以得到每个类对象的逻辑关系如下所示:

(3)测试代码
public class Client { public static void main(String[] args) { //创建目标对象 ConcreteSubject subject = new ConcreteSubject(); //创建多个观察者 ObserverA obs1 = new ObserverA(); ObserverA obs2 = new ObserverA(); ObserverA obs3 = new ObserverA(); //将这三个观察者添加到subject对象的观察者队伍中 subject.registorObserver(obs1); subject.registorObserver(obs2); subject.registorObserver(obs3); //改变subject的状态 subject.setState(300); System.out.println("################"); System.out.println(obs1.getMyState()); System.out.println(obs2.getMyState()); System.out.println(obs3.getMyState()); //改变subject的状态 subject.setState(30); System.out.println("################"); System.out.println(obs1.getMyState()); System.out.println(obs2.getMyState()); System.out.println(obs3.getMyState()); } }
结果图:

tips:在每一次代码调用目标对象subject的setState()方法之后,所有观察者的自身状态都会与目标对象进行同步。将目标对象的状态同步给观察者对象。
3、开发中常见的场景
(1)聊天室程序,服务器转发给所有客户端
(2)网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发
(3)邮件订阅
(4)servlet中,监听器的实现
(5)Android中,广播机制
十一、备忘录模式(memento)
1、场景
(1)Word文档编辑时,忽然电脑死机或断电,再打开时,可以看到Word提示你回复到以前的文档。
(2)管理系统中,公文撤回功能。公文发送出去后,想撤回来。
2、核心
保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。
3、结构
(1)源发器类Originator
(2)备忘录类Memento
(3)负责人类CareTaker:负责保存好的备忘录对象,可以通过增加容器,设置多个“备忘点”。当备忘点较多时:可以将备忘录压栈,以及将多个备忘录对象,序列化和持久化。
4、代码实现
我们根据上述结构,实现备忘录模式的流程。
(1)源发器类Originator
public class Emp { private String name; private int age; private double salary; //进行备忘操作,并返回备忘录对象 public EmpMemento memento() { return new EmpMemento(this); } //恢复为某一个指定的备忘录 public void recovery(EmpMemento mmt) { this.name = mmt.getName(); this.age = mmt.getAge(); this.salary = mmt.getSalary(); } public Emp(String name, int age, double salary) { super(); this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; }}
(2)备忘录类Memento
public class EmpMemento { private String name; private int age; private double salary; public EmpMemento(Emp emp) { this.age = emp.getAge(); this.salary = emp.getSalary(); this.name = emp.getName(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; }}
(3)负责人类CareTaker,管理备忘录
public class CareTaker { private EmpMemento mmt; public EmpMemento getMmt() { return mmt; } public void setMmt(EmpMemento mmt) { this.mmt = mmt; }}
tips:上述备忘录模式的三个基本类构建好之后,其相关之间的关系如以下类图所示:

(4)测试代码
public class Client { public static void main(String[] args) { CareTaker taker = new CareTaker(); Emp emp = new Emp("peng",18,9000); System.out.println("第一次设置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary()); taker.setMmt(emp.memento()); emp.setName("peng2"); emp.setAge(20); emp.setSalary(15000); System.out.println("第二次设置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary()); emp.recovery(taker.getMmt());//恢复备忘录 System.out.println("第三次设置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary()); }}
查看结果图:

tips:测试代码中,我们首先新建一个emp之后,对其基本属性进行设置,然后将其存档,重新设置基本属性值,最后通过备忘录将其恢复。得到的结果与我们预想的结果完全相同。由此完成备忘录模式的模拟。