一、模板方法模式(模板模式)——钩子方法

1、需求-豆浆制作问题

编写制作豆浆的程序,说明如下:

  • 制作豆浆的流程选材——>添加配料——>浸泡——>放到豆浆机打碎。
  • 通过添加不同的配料,可以制作出不同口味的豆浆。
  • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的。
  • 请使用模板方法模式完成(说明:因为模板方法方法,比较简单,很容易就想到这个方案,因此就直接使用,不在使用传统的方案来引出模板方法模式)。

2、模板方法模式基本介绍

基本介绍

模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需重写方法实现,但调用将以抽象类中定义的方式进行。

简单的说,模板方法模式定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。

原理

说明:

  • AbstractClass抽象类,类中实现了模板方法(template()),定义了算法的骨架,具体子类需要去实现其他的抽象方法 operation2、operation3、operation4
  • ConcreteClass 实现抽象方法 operation2、operation3、operation4 ,以完成算法中特定子类的步骤。

3、模板方法模式解决豆浆制作问题

抽象类

//抽象类,表示豆浆
public abstract class SoyaMilk {//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.final void make() {select();addCondiments();soak();beat();}//选材料void select() {System.out.println("第一步:选择好的新鲜黄豆  ");}//添加不同的配料, 抽象方法, 子类具体实现abstract void addCondiments();//浸泡void soak() {System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");}void beat() {System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");}
}

实现抽象类的子类

public class PeanutSoyaMilk extends SoyaMilk {@Overridevoid addCondiments() {System.out.println(" 第二步加入上好的花生 ");}
}public class RedBeanSoyaMilk extends SoyaMilk {@Overridevoid addCondiments() {System.out.println(" 第二步加入上好的红豆 ");}
}

客户端实现

public class Client {public static void main(String[] args) {//制作红豆豆浆System.out.println("----制作红豆豆浆----");SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();redBeanSoyaMilk.make();System.out.println("----制作花生豆浆----");SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();peanutSoyaMilk.make();}
}

输出

----制作红豆豆浆----
第一步:选择好的新鲜黄豆  第二步加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆  第二步加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎  

4、模板方法模式的钩子方法

狗子函数这个称呼是很多开发语言中都会涉及到的一个东西,我的理解是系统处理消息时预先设置好一个函数,就好像某一个周期,这个周期是系统自动执行的,那么这个周期怎样让人为干预他的进程呢,就像是这样。如下图。

假设有一个线程我们大概分成了这三个部分,初始化、运行、结束,这个线程就是向里面传入一个数据,然后就按部就班的执行这三个步骤。这时我们突然想干预这个线程的执行过程,或者为后面的业务需求增加几个函数(目前可能用不到,后续的业务需求可能会用到),这个时候就要在适当的未知增加几个预留函数,这个函数就叫做狗子函数。

钩子函数当系统消息触发时,自动会调用,不是用户自己触发的,使用时直接编写函数体。

豆浆制作增加狗子函数

抽象类-needCondiments()就是狗子函数

//抽象类,表示豆浆
public abstract class SoyaMilk {//模板方法, make , 模板方法可以做成final , 不让子类去覆盖.final void make() {select();if (needCondiments()) {addCondiments();}soak();beat();}//选材料void select() {System.out.println("第一步:选择好的新鲜黄豆  ");}//添加不同的配料, 抽象方法, 子类具体实现abstract void addCondiments();//狗子函数-是否需要添加配料boolean needCondiments(){return true;}//浸泡void soak() {System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");}void beat() {System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");}
}

增加一个纯豆浆的子类

//纯豆浆
public class OnlySoyaMilk extends SoyaMilk {@Overridevoid addCondiments() {}@Overrideboolean needCondiments() {return false;}
}

Client

public class Client {public static void main(String[] args) {//制作红豆豆浆System.out.println("----制作红豆豆浆----");SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();redBeanSoyaMilk.make();System.out.println("----制作花生豆浆----");SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();peanutSoyaMilk.make();System.out.println("----制作纯豆浆----");SoyaMilk onlySoyaMilk = new OnlySoyaMilk();onlySoyaMilk.make();}
}

输出

----制作红豆豆浆----
第一步:选择好的新鲜黄豆  第二步加入上好的红豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作花生豆浆----
第一步:选择好的新鲜黄豆  第二步加入上好的花生
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎
----制作纯豆浆----
第一步:选择好的新鲜黄豆
第三步, 黄豆和配料开始浸泡, 需要3小时
第四步:黄豆和配料放到豆浆机去打碎

5、模板方法模式在 Spring 框架应用的源码分析

Spring IOC 容器初始化时运用到的模板方法模式。

  • ConfigurableApplicationContext 接口中申明了一个模板方法 refresh() 。
  • AbstractApplicationContext 类实现了 ConfigurableApplicationContext 接口中的 refresh()  方法。它相当于我们例子中的抽象类 SyoaMilk。
  • AbstractApplicationContext 的refresh()函数体中使用了getBeanFactory() 、refreshBeanFactory() 、postProcessBeanFactory()、onReFresh()。getBeanFactory() 、refreshBeanFactory() 是抽象方法,postProcessBeanFactory()、onReFresh() 是钩子方法。
  • GenericApplicationContext 实现了getBeanFactory() 、refreshBeanFactory()。它相当于我们例子中的 抽象类子类PeanutSoyaMilk。
  • AbstractRefreshableApplicationContext 是另外一个抽象类子类。
  • ......

6、 模板方法模式的注意事项和细节

  • 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。

  • 实现了最大化代码优化。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

  • 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。

  • 一般模板方法都加上final 关键字, 防止子类重写模板方法。

  • 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理。

二、命令模式(源码讲解)

1、智能生活项目需求

  • 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app 就可以控制对这些家电工作。

  • 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。

  • 要实现一个app 控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app 调用,这时就可以考虑使用命令模式。

  • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来。

  • 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品。

2、命令模式介绍

基本介绍

命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需要在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计。

命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。

在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求,同时命令模式也支持可撤销的操作。

通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。

命令模式原理

说明:

  • Invoker 是调用者角色。
  • Command 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类。
  • Receiver 是接收者角色,知道如何实施和执行一个请求相关的操作。
  • ConcreteCommand 将一个接收者对象与一个动作绑定,调用接收者相应的操作,实现 execute。

3、命令模式解决智能生活项目

编写程序,使用命令模式完成前面的智能家电项目。

类图如下所示

代码实现

创建命令接口

//创建命令接口
public interface Command {//执行动作(操作)void execute();//撤销动作(操作)void undo();
}

创建接收者(灯 - LightReceiver )

public class LightReceiver {public void on() {System.out.println(" 电灯打开了.. ");}public void off() {System.out.println(" 电灯关闭了.. ");}
}

创建命令具体实现(灯打开命令、灯关闭命令)

public class LightOnCommand implements Command {//聚合LightReceiverprivate LightReceiver lightReceiver;public LightOnCommand(LightReceiver lightReceiver) {super();this.lightReceiver = lightReceiver;}@Overridepublic void execute() {//调用接收者的方法lightReceiver.on();}@Overridepublic void undo() {// 调用接收者的方法lightReceiver.off();}
}public class LightOffCommand implements Command {//聚合LightReceiverprivate LightReceiver lightReceiver;public LightOffCommand(LightReceiver lightReceiver) {super();this.lightReceiver = lightReceiver;}@Overridepublic void execute() {//调用接收者的方法lightReceiver.off();}@Overridepublic void undo() {// 调用接收者的方法lightReceiver.on();}
}

创建一个空命令

/*** 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做* 其实,这样是一种设计模式, 可以省掉对空判断*/
public class NoCommand implements Command {@Overridepublic void execute() {}@Overridepublic void undo() {}
}

创建调用者(遥控器)

//调用者 - 遥控器
public class Invoker {// 开 按钮的命令数组Command[] onCommands;Command[] offCommands;// 执行撤销的命令Command undoCommand;// 构造器,完成对按钮初始化public Invoker() {onCommands = new Command[5];offCommands = new Command[5];for (int i = 0; i < 5; i++) {onCommands[i] = new NoCommand();offCommands[i] = new NoCommand();}}// 给我们的按钮设置你需要的命令public void setCommand(int no, Command onCommand, Command offCommand) {onCommands[no] = onCommand;offCommands[no] = offCommand;}// 按下开按钮public void onButtonWasPushed(int no) {// 找到你按下的开的按钮, 并调用对应方法onCommands[no].execute();// 记录这次的操作,用于撤销undoCommand = onCommands[no];}// 按下关按钮public void offButtonWasPushed(int no) {// 找到你按下的关的按钮, 并调用对应方法offCommands[no].execute();// 记录这次的操作,用于撤销undoCommand = offCommands[no];}// 按下撤销按钮public void undoButtonWasPushed() {undoCommand.undo();}
}

客户端

public class Client {public static void main(String[] args) {//使用命令设计模式,完成通过遥控器,对电灯的操作//创建电灯的对象(接受者)LightReceiver lightReceiver = new LightReceiver();//创建电灯相关的开关命令LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);//需要一个遥控器Invoker invoker = new Invoker();invoker.setCommand(0, lightOnCommand, lightOffCommand);System.out.println("--------按下灯的开按钮-----------");invoker.onButtonWasPushed(0);System.out.println("--------按下灯的关按钮-----------");invoker.offButtonWasPushed(0);System.out.println("--------按下撤销按钮-----------");invoker.undoButtonWasPushed();}
}

输出

--------按下灯的开按钮-----------电灯打开了..
--------按下灯的关按钮-----------电灯关闭了..
--------按下撤销按钮-----------电灯打开了.. 

添加电视开关命令

使用命令模式创建的代码,符合开关原则。当我们需要添加 添加电视开关命令 时,我们只需要实现电视接收者、电视开、电视关命令即可。

电视命令接收者

public class TvReceiver {public void on() {System.out.println(" 电视打开了.. ");}public void off() {System.out.println(" 电视关闭了.. ");}
}

电视开、关命令

public class TvOnCommand implements Command {//聚合LightReceiverprivate TvReceiver tvReceiver;public TvOnCommand(TvReceiver tvReceiver) {super();this.tvReceiver = tvReceiver;}@Overridepublic void execute() {//调用接收者的方法tvReceiver.on();}@Overridepublic void undo() {// 调用接收者的方法tvReceiver.off();}
}public class TvOffCommand implements Command {//聚合LightReceiverprivate TvReceiver tvReceiver;public TvOffCommand(TvReceiver tvReceiver) {super();this.tvReceiver = tvReceiver;}@Overridepublic void execute() {//调用接收者的方法tvReceiver.off();}@Overridepublic void undo() {// 调用接收者的方法tvReceiver.on();}
}

客户端调用,增加电视机命令的调用

public class Client {public static void main(String[] args) {//使用命令设计模式,完成通过遥控器,对电灯的操作//创建电灯的对象(接受者)LightReceiver lightReceiver = new LightReceiver();//创建电灯相关的开关命令LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);//需要一个遥控器Invoker invoker = new Invoker();invoker.setCommand(0, lightOnCommand, lightOffCommand);System.out.println("--------按下灯的开按钮-----------");invoker.onButtonWasPushed(0);System.out.println("--------按下灯的关按钮-----------");invoker.offButtonWasPushed(0);System.out.println("--------按下撤销按钮-----------");invoker.undoButtonWasPushed();System.out.println("=========使用遥控器操作电视机==========");TvReceiver tvReceiver = new TvReceiver();TvOffCommand tvOffCommand = new TvOffCommand(tvReceiver);TvOnCommand tvOnCommand = new TvOnCommand(tvReceiver);//给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作invoker.setCommand(1, tvOnCommand, tvOffCommand);System.out.println("--------按下电视机的开按钮-----------");invoker.onButtonWasPushed(1);System.out.println("--------按下电视机的关按钮-----------");invoker.offButtonWasPushed(1);System.out.println("--------按下撤销按钮-----------");invoker.undoButtonWasPushed();}
}

输出

--------按下灯的开按钮-----------电灯打开了..
--------按下灯的关按钮-----------电灯关闭了..
--------按下撤销按钮-----------电灯打开了..
=========使用遥控器操作电视机==========
--------按下电视机的开按钮-----------电视打开了..
--------按下电视机的关按钮-----------电视关闭了..
--------按下撤销按钮-----------电视打开了.. Process finished with exit code 0

4、命令模式在Spring框架 JdbcTemplate 的源码解析

  • StatementCallback 接口,类似命令接口(Command)。
  • class QueryStatementCallback implements StatementCallback, SqlProvider ,匿名内部类,实现了命令接口,同时也充当命令接收者。
  • 命令调用者是 JdbcTemplate ,其中 execute(StatementCallback action) 方法中,调用 action.doInStatement 方法。不同的实现 StatementCallback 接口的对象,对应不同的 doInStatement 实现逻辑。
  • 另外实现 StatementCallback 命令接口的子类还有 QueryStatementCallback、UpdateStatementCallback、BatchUpdateStatementCallback、ExecuteStatementCallback。

5、命令模式的注意事项和细节

1、将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute() 方法就可以让接收者工作,而不必知道具体的接收者是谁、如何实现,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

2、容易设计一个命令队列。只要把命令对象放到队列,就可以多线程的执行命令。

3、容易实现对请求的撤销和重做。

4、命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在使用的时候需要注意。

5、空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。

6、令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS 命令)订单的撤销/恢复、触发-反馈机制。

  • 有一个命令接口,由子类各种命令(如电视打开命令)去实现,
  • 子类各种命令聚合了接收者(被调用者)的操作(如打开电视,打开灯光),
  • 遥控器(调用者)通过聚合命令接口来调用他的子类(相当于命令执行,因为子类是命令接口的子类又有被调用者的操作)。
  • 客户端要创建遥控器,必须有命令子类,必须要有接收者。

三、访问者模式-双分派

1、测评系统需求

将观众分为男人和女人,对歌手进行评测,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)。

2、传统方式解决测评系统需求

  • 定义抽象类 Person
  • Person子类为 Man、Women
  • Man、Women 中定义 不同的评价方法(成功、失败、待定)

传统方式问题分析

  • 如果系统比较小,还是ok的,但是考虑系统增加越来越多的功能时,对代码改动较大,违反了 OCP 原则,不利于维护。
  • 扩展性不好,比如增加了新的人员类型,或者管理方法,都不好做(例如加个待定,必须改类)
  • 引出我们的设计模式-访问者模式。

3、访问者模式介绍

访问者模式(Visitor),封装一些用于某种数据结构的各元素的操作,它可以在不改变的前提下定义作用于这些元素的新的操作。

主要将数据结构与数据操作分离,解决数据结构和操作的耦合性问题。

访问者模式的基本工作原理是:在被访问的类里面加一个对外提供待访问者的接口

访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。

原理类图如下:

原理类图说明:

  • Visitor 是抽象访问者,为该对象结构中的 ConcreteElement 的每个类申明一个 visit 操作。
  • ConcreteVisitor:是一个具体的访问者,它实现Visitor声明的操作,是每个操作实现的部分。
  • ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素。
  • Element 定义一个accept方法,接收一个访问者对象。
  • ConcreteElement 为具体元素,实现了 accept 方法。

4、访问者模式解决测评系统需求

说明

  • 将人分为男人和女人,对歌手进行评测,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)。

代码实现

抽象访问者和其具体实现

//访问者
public interface Action {void getManResult(Man man);void getWomenResult(Women man);
}public class Success implements Action {@Overridepublic void getManResult(Man man) {System.out.println(" 男人给的评价该歌手很成功 !");}@Overridepublic void getWomenResult(Women man) {System.out.println(" 女人给的评价该歌手很成功 !");}
}public class Failure implements Action {@Overridepublic void getManResult(Man man) {System.out.println(" 男人给的评价该是歌手演唱失败 !");}@Overridepublic void getWomenResult(Women man) {System.out.println(" 女人给的评价是该歌手演唱失败 !");}
}

Element 类和其具体实现

// 元素,接受对象
public abstract class Person {//提供一个方法,让访问者可以访问public abstract void accept(Action action);
}public class Man extends Person {@Overridepublic void accept(Action action) {action.getManResult(this);}
}public class Women extends Person {@Overridepublic void accept(Action action) {action.getWomenResult(this);}
}

数据结构类

//数据结构,维护很多人
public class ObjectStructure {//维护了一个集合private List<Person> personList = new LinkedList<Person>();//增加到Listpublic void attach(Person person) {personList.add(person);}//移除public void detach(Person person) {personList.remove(person);}//显示测评public void display(Action action) {for (Person person : personList) {person.accept(action);}}
}

客户端

public class Client {public static void main(String[] args) {//创建ObjectStructureObjectStructure objectStructure = new ObjectStructure();//向数据结构中增加人员objectStructure.attach(new Man());objectStructure.attach(new Women());//成功Success success = new Success();objectStructure.display(success);System.out.println("===============");Failure failure = new Failure();objectStructure.display(failure);}
}

输出

 男人给的评价该歌手很成功 !女人给的评价该歌手很成功 !
===============男人给的评价该是歌手演唱失败 !女人给的评价是该歌手演唱失败 !

增加一个待定的评价

此时增加一个待定的评价非常简单,只需要定义一个类实现 Action 接口,即可在客户端进行使用。

public class Wait implements Action {@Overridepublic void getManResult(Man man) {System.out.println(" 男人给的评价是待定 !");}@Overridepublic void getWomenResult(Women man) {System.out.println(" 女人给的评价是待定 !");}
}

客户端增加如下代码即可

        System.out.println("===============");Wait wait = new Wait();objectStructure.display(wait);

双分派说明

所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求种类和两个接收者的类型。

以上述实例为例,假如我们要添加 一个Wait 状态类,考察 Man类和Women类的反应,由于使用了双分派,只需增加一个 Action 子类即可在客户端调用,不需要改动任何其他类的代码。

5、总结

优点:

  • 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高。

  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统。

缺点:

  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难。

  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素。

  • 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。

四、迭代器模式(iterator)

1、需求-展示学校院系结构

需求是这样的,要在一个页面中展示出学校的院系组成,一个学校有多个学院、一个学院有多个系。如图:

传统设计方案

传统方式问题分析

  • 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的。

  • 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的遍历的操作。

  • 解决方案:=> 迭代器模式。

2、迭代器模式介绍

基本介绍

如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些元素的时候,就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。

迭代器模式提供一种遍历集合元素的统一接口,用一致的方法遍历集合,不需要知道集合对象的底层表示,即:不暴露其内部的结构。

原理

原理类图说明:

  • Iterator:迭代器接口,是系统提供,含有 hasNext、next、remove
  • ConcreteIterator:具体的迭代器,管理迭代
  • Aggregate:一个统一的聚合接口,将客户端和具体聚合解耦。
  • ConcreteAggregate:具体持有对象聚合,并提供一个方法,返回一个迭代器,该迭代器可正确遍历。
  • Client:客户端,通过 Iterator、Aggregate 依赖子类。

3、迭代器模式处理展示学校院系结构需求

编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学院的院系组成,一个学校有多个学院,一个学院有多个系。

持有的最小对象是 Department

public class Department {private String name;private String des;public Department(String name, String des) {this.name = name;this.des = des;}public void setName(String name) {this.name = name;}public void setDes(String des) {this.des = des;}public String getName() {return name;}public String getDes() {return des;}
}

迭代器接口,及具体的迭代器

public interface Iterator<E> {boolean hasNext();E next();void remove();
}public class ComputerCollegeIterator implements Iterator<Department> {//这里我们以数组的形式存放数据Department[] departments;//遍历的未知int position = 0;public ComputerCollegeIterator(Department[] departments) {this.departments = departments;}//判断是否还有下一个元素@Overridepublic boolean hasNext() {if (position>=departments.length || departments[position]==null){return false;}return true;}@Overridepublic Department next() {Department department = departments[position];position++;return department;}//默认空实现@Overridepublic void remove() {}
}public class InfoColleageIterator implements Iterator<Department> {//这里我们以List的形式存放数据private List<Department> departmentList;//索引private int position = -1;public InfoColleageIterator(List<Department> departmentList) {this.departmentList = departmentList;}@Overridepublic boolean hasNext() {if (position >= departmentList.size()-1){return false;}position++;return true;}@Overridepublic Department next() {Department department = departmentList.get(position);return department;}//空实现remove@Overridepublic void remove() {}
}

聚合接口,及具体实现类(存放数据及返回迭代器)

//聚合接口
public interface College {String getName();//增加系的方法void addDepartment(String name, String desc);//返回一个迭代器,遍历Iterator  createIterator();
}public class ComputerCollege implements College {Department[] departments;int numOfDepartment = 0 ;// 保存当前数组的对象个数public ComputerCollege() {departments = new Department[5];addDepartment("Java专业", " Java专业 ");addDepartment("PHP专业", " PHP专业 ");addDepartment("大数据专业", " 大数据专业 ");}@Overridepublic String getName() {return "计算机学院";}@Overridepublic void addDepartment(String name, String desc) {Department department = new Department(name, desc);departments[numOfDepartment] = department;numOfDepartment++;}@Overridepublic Iterator createIterator() {return new ComputerCollegeIterator(departments);}
}public class InfoCollege implements College {List<Department> departments;public InfoCollege() {departments = new ArrayList<Department>();addDepartment("信息安全专业", " 信息安全专业 ");addDepartment("网络安全专业", " 网络安全专业 ");addDepartment("服务器安全专业", " 服务器安全专业 ");}@Overridepublic String getName() {return "信息学院";}@Overridepublic void addDepartment(String name, String desc) {departments.add(new Department(name,desc));}@Overridepublic Iterator createIterator() {return new InfoColleageIterator(departments);}
}

输出工具类,用来遍历学院和院系

public class OutPutImpl {//学院集合List<College> collegeList;public OutPutImpl(List<College> collegeList) {this.collegeList = collegeList;}//遍历所有学院,然后调用printDepartment 输出各个学院的系public void printCollege() {//从collegeList 取出所有学院, Java 中的 List 已经实现Iteratorjava.util.Iterator<College> iterator = collegeList.iterator();while(iterator.hasNext()) {//取出一个学院College college = iterator.next();System.out.println("=== "+college.getName() +"=====" );printDepartment(college.createIterator()); //得到对应迭代器}}//输出 学院输出 系public void printDepartment(Iterator iterator) {while(iterator.hasNext()) {Department d = (Department)iterator.next();System.out.println(d.getName());}}
}

客户端

public class Client {public static void main(String[] args) {// TODO Auto-generated method stub//创建学院List<College> collegeList = new ArrayList<College>();ComputerCollege computerCollege = new ComputerCollege();InfoCollege infoCollege = new InfoCollege();collegeList.add(computerCollege);collegeList.add(infoCollege);OutPutImpl outPutImpl = new OutPutImpl(collegeList);outPutImpl.printCollege();}
}

输出

=== 计算机学院=====
Java专业
PHP专业
大数据专业
=== 信息学院=====
信息安全专业
网络安全专业
服务器安全专业

4、迭代器模式在 JDK - ArrayList 集合应用的源码分析

  • Iterator 是迭代器接口。
  • ArrayList 的内部类 Itr ,它是 Iterator 接口的具体实现。
  • List 充当了聚合接口,它含有一个 Iterator() 方法,返回一个迭代器对象。
  • ArrayList 是实现聚合接口List的子类,它实现了 Iterator。

5、总结

优点:

  • 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
  • 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能渠道迭代器,而不会知道聚合的具体组成。
  • 提供了一种设计思想,就是一个类应该只有一个引起变化的原因。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历的方式改变的话,只影响到迭代器。
  • 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式。

缺点:

  • 每个聚合对象都要一个迭代器,会生成很多个迭代器不好管理。

迭代器模式含有两个接口,一个聚合接口,一个迭代器接口。相应的迭代器(迭代器的子类)聚合到聚合接口的子类(由最小同一类型组合,例如:Object、专业类)。

五、观察者模式(Observer)

1、天气预报项目需求

  • 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
  • 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
  • 提供温度、气压和湿度的接口
  • 测量数据更新时,要能实时的通知给第三方

2、普通方案解决天气预报项目需求

  • 通过 getXxx 方法,让第三方可以接入,并得到先关信息。
  • 当数据有更新时, 气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送。

示意图如下

CurrentConditions(当前的天气情况),可以理解成是我们气象局的网站//推送。

代码实现

第三方

/*** 显示当前天气情况(可以理解成是气象站自己的网站)*/
public class CurrentConditions {// 温度,气压,湿度private float temperature;private float pressure;private float humidity;//更新 天气情况,是由 WeatherData 来调用,我使用推送模式public void update(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;display();}//显示public void display() {System.out.println("***Today mTemperature: " + temperature + "***");System.out.println("***Today mPressure: " + pressure + "***");System.out.println("***Today mHumidity: " + humidity + "***");}
}

核心类

/*** 核心类* 1. 包含最新的天气情况信息* 2. 含有 CurrentConditions 对象* 3. 当数据有更新时,就主动的调用   CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息*/
public class WeatherData {private float temperatrue;private float pressure;private float humidity;//加入的第三方private CurrentConditions currentConditions;public WeatherData(CurrentConditions currentConditions) {this.currentConditions = currentConditions;}public void dataChange() {//调用 接入方的 updatecurrentConditions.update(temperatrue, pressure, humidity);}//当数据有更新时,就调用 setDatapublic void setData(float temperature, float pressure, float humidity) {this.temperatrue = temperature;this.pressure = pressure;this.humidity = humidity;//调用dataChange, 将最新的信息 推送给 接入方 currentConditionsdataChange();}
}

客户端

public class Client {public static void main(String[] args) {//创建接入方 currentConditionsCurrentConditions currentConditions = new CurrentConditions();//创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData中WeatherData weatherData = new WeatherData(currentConditions);//更新天气情况weatherData.setData(30, 150, 40);//天气情况变化System.out.println("============天气情况变化=============");weatherData.setData(40, 160, 20);}
}

输出

***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
============天气情况变化=============
***Today mTemperature: 40.0***
***Today mPressure: 160.0***
***Today mHumidity: 20.0***

问题分析

  • 其他第三方接入气象站获取数据的问题

  • 无法在运行时动态的添加第三方(新浪网站)

  • 违反ocp 原则=>观察者模式

3、观察者模式介绍

观察者模式类似订牛奶业务

  • 奶站:Subject
  • 用户:Observer

Subject:登记注册、移除和通知

  • registerObserver 注册

  • removeObserver 移除

  • notifyObservers() 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定。

Observer:接收输入

观察者模式:对象之间多对一依赖的一种设计方案, 被依赖的对象为 Subject,依赖的对象为Observer,Subject 通知 Observer变化,比如这里的奶站就是 Subject,是一的一方;用户是Observer,是多的一方。

4、观察者模式解决天气预报项目需求

类图如下

观察者

//观察者接口,由观察者实现
public interface Observer {void update(float temperature, float pressure, float humidity);
}/*** 显示当前天气情况(可以理解成是气象站自己的网站)*/
public class CurrentConditions implements Observer {// 温度,气压,湿度private float temperature;private float pressure;private float humidity;//更新 天气情况,是由 WeatherData 来调用,我使用推送模式@Overridepublic void update(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;display();}//显示public void display() {System.out.println("=================CurrentConditions=================");System.out.println("***Today mTemperature: " + temperature + "***");System.out.println("***Today mPressure: " + pressure + "***");System.out.println("***Today mHumidity: " + humidity + "***");}
}/*** 百度网站*/
public class BaiduSite implements Observer {// 温度,气压,湿度private float temperature;private float pressure;private float humidity;//更新 天气情况,是由 WeatherData 来调用,我使用推送模式@Overridepublic void update(float temperature, float pressure, float humidity) {this.temperature = temperature;this.pressure = pressure;this.humidity = humidity;display();}//显示public void display() {System.out.println("=================百度网站=================");System.out.println("***Today mTemperature: " + temperature + "***");System.out.println("***Today mPressure: " + pressure + "***");System.out.println("***Today mHumidity: " + humidity + "***");}
}

主题

//接口,由 WeatherData 实现
public interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}/*** 类是核心* 1. 包含最新的天气情况信息* 2. 含有 CurrentConditions 对象* 3. 当数据有更新时,就主动的调用   CurrentConditions对象update方法(含 display), 这样他们(接入方)就看到最新的信息*/
public class WeatherData implements Subject {private float temperatrue;private float pressure;private float humidity;//观察者集合private List<Observer> observers;public WeatherData() {this.observers = new ArrayList<>();}//当数据有更新时,就调用 setDatapublic void setData(float temperature, float pressure, float humidity) {this.temperatrue = temperature;this.pressure = pressure;this.humidity = humidity;//调用 notifyObservers, 将最新的信息 推送给 观察者集合notifyObservers();}//注册一个观察者@Overridepublic void registerObserver(Observer o) {if (!observers.contains(o)) {observers.add(o);}}//移除一个观察者@Overridepublic void removeObserver(Observer o) {if (observers.contains(o)) {observers.remove(o);}}//遍历所有的观察者,并通知@Overridepublic void notifyObservers() {for (Observer o:observers){o.update(this.temperatrue, this.pressure, this.humidity);}}
}

客户端

public class Client {public static void main(String[] args) {//创建 WeatherDataWeatherData weatherData = new WeatherData();//创建观察者CurrentConditions currentConditions = new CurrentConditions();BaiduSite baiduSite = new BaiduSite();//注册到weatherDataweatherData.registerObserver(currentConditions);weatherData.registerObserver(baiduSite);//更新天气情况System.out.println("通知各个注册的观察者, 看看信息");weatherData.setData(30, 150, 40);System.out.println("\n移除 CurrentConditions 观察者\n");weatherData.removeObserver(currentConditions);System.out.println("通知各个注册的观察者, 看看信息");weatherData.setData(30, 150, 40);}
}

输出

通知各个注册的观察者, 看看信息
=================CurrentConditions=================
***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
=================百度网站=================
***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***移除 CurrentConditions 观察者通知各个注册的观察者, 看看信息
=================百度网站=================
***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***

观察者模式的好处:

  • 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。

  • 这样,我们增加观察者(比如新浪网、腾讯网),就不需要去修改核心类WeatherData 不会修改代码,遵守了ocp 原则。

5、观察者模式在Jdk源码中的应用

//jdk观察者接口
public interface Observer {void update(Observable o, Object arg);
}//jdk 主题类
public class Observable {...private Vector<Observer> obs;/** Construct an Observable with zero Observers. */public Observable() {obs = new Vector<>();}public synchronized void addObserver(Observer o) {if (o == null)throw new NullPointerException();if (!obs.contains(o)) {obs.addElement(o);}}public synchronized void deleteObserver(Observer o) {obs.removeElement(o);}public void notifyObservers() {notifyObservers(null);}
}

源码分析:

  • Observable的作用和地位等价与前面讲过的 Subject
  • Observable 是类不是接口,类中以及实现了核心的方法,即管理 Observer 的方法 addObserver、deleteObserver、notifyObservers
  • Observer 地位等价我们前面讲过的 Observer,有 update 方法
  • Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

六、中介者模式(Mediator Pattern)

1、需求-智能家电项目

智能家电包括各种设备:闹钟、咖啡机、电视、窗帘等。

主人要看电视,各个设备可以协同工作,自动完成看电视的准备,比如流程为:闹钟响起 -> 咖啡机开始做咖啡 -> 窗帘自动落下 -> 电视机开始播放。

2、传统方式解决需求-智能家电项目

闹钟发送信息给咖啡机,咖啡机发送信息给电视机。。。。

问题分析:

  • 当各电器对象有多重状态改变时,互相之间的调用关系会比较复杂。
  • 各个电器对象彼此联系,你中有我,我中有你,不利于松耦合。
  • 各个电器对象之间所传递的参数,容易混乱。
  • 当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想
  • 考虑使用 中介者模式。

3、中介者模式介绍

基本介绍

中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示地互相引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。

中介者模式属于行为模式,使代码易于维护。

比如 MVC 模式,C(Controller)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用。

原理类图

  • Mediator 就是抽象中介者,定义了同事对象到中介者对象的接口。
  • Colleague 是抽象同事类
  • ConcreteMediator 是具体的中介者对象,实现抽象方法,它需要知道所有 的具体的同事类,即以一个集合来管理(HashMap),并接收同事对象消息,完成响应的任务。
  • ConcreteColleague 具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事的行为,但是他们都依赖中介者对象。

4、中介者模式解决智能家电项目

类图如下

中介者模式操作流程:

  • 创建  ConcreteMediator 对象
  • 创建各个同事对象,比如 Alarm、CoffeeMachine、TV、.......
  • 在创建同事类对象的时候,就直接通过构造器,加入到 ConcreteMediatorMap 的 colleagueMap 中
  • 同事类对象可以调用 sendMessage,最终会去调用 ConcreteMediator 的 getMessage 方法
  • getMessage 会根据收到的同事对象发出的消息,来协调其他的同事对象,完成任务。
  • 可以看到 getMessage 是核心方法,完成相应的任务。

代码实现

中介者类

//抽象中介者
public abstract class Mediator {//将同事对象加入到集合中public abstract void register(String colleagueName, Colleague colleague);//接收到消息,具体的同事对象发出public abstract void getMessage(int stateChange, String colleagueName);public abstract void sendMessage();
}//具体的中介者类
public class ConcreteMediator extends Mediator {//集合,放入所有的同事对象private HashMap<String, Colleague> colleagueMap;private HashMap<String, String> interMap;public ConcreteMediator() {colleagueMap = new HashMap<String, Colleague>();interMap = new HashMap<String, String>();}@Overridepublic void register(String colleagueName, Colleague colleague) {colleagueMap.put(colleagueName, colleague);// TODO Auto-generated method stubif (colleague instanceof Alarm) {interMap.put("Alarm", colleagueName);} else if (colleague instanceof CoffeeMachine) {interMap.put("CoffeeMachine", colleagueName);} else if (colleague instanceof Tv) {interMap.put("TV", colleagueName);} else if (colleague instanceof Curtains) {interMap.put("Curtains", colleagueName);}}//具体中介者的核心方法//1. 根据得到消息,完成对应任务//2. 中介者在这个方法,协调各个具体的同事对象,完成任务@Overridepublic void getMessage(int stateChange, String colleagueName) {//处理脑钟发出的消息if (colleagueMap.get(colleagueName) instanceof Alarm) {if (stateChange == 0) {//闹钟刚开始响,会自动打开电视机和咖啡机String coffeeName = interMap.get("CoffeeMachine");CoffeeMachine coffeeMachine = (CoffeeMachine) colleagueMap.get(coffeeName);coffeeMachine.startCoffee();String tvName = interMap.get("TV");Tv tv = (Tv) colleagueMap.get(tvName);tv.startTv();} else if (stateChange == 1) {//关闭闹钟,会自动关闭电视String tvName = interMap.get("TV");Tv tv = (Tv) colleagueMap.get(tvName);tv.stopTv();}} else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {//收到咖啡冲完的消息会自动拉下窗帘String curtainsName = interMap.get("Curtains");Curtains curtains = (Curtains) colleagueMap.get(curtainsName);curtains.upCurtains();} else if (colleagueMap.get(colleagueName) instanceof Tv) {//如果是以TV发出的消息,这里处理...} else if (colleagueMap.get(colleagueName) instanceof Curtains) {//如果是以窗帘发出的消息,这里处理...}}@Overridepublic void sendMessage() {}
}

同事类

//同事抽象类
public abstract class Colleague {private Mediator mediator;protected String name;public Colleague(Mediator mediator, String name) {this.mediator = mediator;this.name = name;}public Mediator getMediator() {return mediator;}public abstract void sendMessage(int stateChange);
}public class Alarm extends Colleague {public Alarm(Mediator mediator, String name) {super(mediator, name);//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]mediator.register(name, this);}public void sendAlarm(int stateChange) {sendMessage(stateChange);}@Overridepublic void sendMessage(int stateChange) {this.getMediator().getMessage(stateChange, this.name);}
}public class CoffeeMachine extends Colleague {public CoffeeMachine(Mediator mediator, String name) {super(mediator, name);//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]mediator.register(name, this);}public void startCoffee() {System.out.println(" 开始冲咖啡 ");}public void finishCoffee() {System.out.println(" 5分钟后 ");System.out.println(" 咖啡冲好了 ");sendMessage(0);}@Overridepublic void sendMessage(int stateChange) {this.getMediator().getMessage(stateChange, this.name);}
}public class Curtains extends Colleague {public Curtains(Mediator mediator, String name) {super(mediator, name);//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]mediator.register(name, this);}public void upCurtains() {System.out.println("正在拉窗帘");}@Overridepublic void sendMessage(int stateChange) {this.getMediator().getMessage(stateChange, this.name);}
}public class Tv extends Colleague {public Tv(Mediator mediator, String name) {super(mediator, name);//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]mediator.register(name, this);}public void startTv() {System.out.println("打开电视机");}public void stopTv() {System.out.println("关闭电视机");}@Overridepublic void sendMessage(int stateChange) {this.getMediator().getMessage(stateChange, this.name);}
}

客户端

public class ClientTest {public static void main(String[] args) {//创建一个中介者对象Mediator mediator = new ConcreteMediator();//创建 Alarm 并且加入到 ConcreteMediator 的 hashMapAlarm alarm = new Alarm(mediator, "alarm");//创建了CoffeeMachine 对象,并  且加入到  ConcreteMediator 对象的HashMapCoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine");//创建 Curtains , 并  且加入到  ConcreteMediator 对象的HashMapCurtains curtains = new Curtains(mediator, "curtains");//创建 Tv , 并  且加入到  ConcreteMediator 对象的HashMapTv tV = new Tv(mediator, "TV");//让闹钟发出消息alarm.sendAlarm(0);coffeeMachine.finishCoffee();alarm.sendAlarm(1);}
}

输出

 开始冲咖啡
打开电视机5分钟后 咖啡冲好了
正在拉窗帘
关闭电视机

5、总结

  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦

  • 减少类间依赖,降低了耦合,符合迪米特原则

  • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响

  • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

七、备忘录模式(Memento Pattern)

游戏角色有攻击力和防御力,在大战boss前保存自身的状态(攻击力和防御力),当大战boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

1、传统方案解决游戏角色恢复

问题分析:

  • 一个对象就对应一个保存对象的状态的对象,这样当我们游戏的对象很多时,不利于管理,开销也很大。
  • 传统的方式是简单地做备份,new 出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
  • 解决方案 -> 备忘录模式。

2、备忘录模式介绍

基本介绍

1、备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

2、可以这样理解备忘录模式:现实生活中的备忘录是用来记录某些要做的事情,或者是记录已经达成共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。

3、备忘录模式属于行为模式

原理类图

说明:

  • originator:对象(需要保存状态的对象)
  • Memento:备忘录对象,负责保存好记录,即 originator 内部状态。
  • Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率。
  • 如果希望保存多个originator对象的不同时间的状态,也可以,只需要HashMap<String,集合>

保存操作:需要保存状态的对象创建备忘录对象,在把备忘录对象保存到守护者对象。

读取操作:从守护者对象读取备忘录对象,录入需要保存状态的对象。

代码实现

备忘录对象

public class Memento {private String state;//构造器public Memento(String state) {this.state = state;}public String getState() {return state;}
}

原始对象

public class Originator {private String state;public void setState(String state) {this.state = state;}public String getState() {return state;}//编写一个方法,可以保存一个状态对象 Mementopublic Memento saveMemento(){return  new Memento(state);}// 通过备忘录对象,恢复状态public void getFromMemento(Memento memento) {state = memento.getState();}
}

守护者对象

public class Caretaker {//list集合中有很多的备忘录对象private List<Memento> mementos = new ArrayList<>();public void add(Memento memento) {mementos.add(memento);}//取到第 index 个 Originator 的备忘录对象public Memento get(int index) {return mementos.get(index);}
}

客户端

public class Client {public static void main(String[] args) {Originator originator = new Originator();Caretaker caretaker = new Caretaker();originator.setState(" 状态#1 攻击力 100 ");//保存了当前的状态caretaker.add(originator.saveMemento());originator.setState(" 状态#2 攻击力 80 ");caretaker.add(originator.saveMemento());originator.setState(" 状态#3 攻击力 50 ");caretaker.add(originator.saveMemento());System.out.println(" 当前的状态是:" + originator.getState());//希望恢复到状态1System.out.println(" 恢复到状态#1 ");originator.getFromMemento(caretaker.get(0));System.out.println("当前的状态是:" + originator.getState());}
}

输出

 当前的状态是: 状态#3 攻击力 50 恢复到状态#1
当前的状态是: 状态#1 攻击力 100 

3、备忘录模式解决游戏角色恢复

游戏角色有攻击力和防御力,在大战Boss 前保存自身的状态(攻击力和防御力),当大战Boss 后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

原理类图

代码实现

需要保存状态的对象 (游戏角色)

public class GameRole {//攻击力private int vit;//防御力private int def;public void setVit(int vit) {this.vit = vit;}public void setDef(int def) {this.def = def;}public int getVit() {return vit;}public int getDef() {return def;}//将当前状态保存到 Memento,并返回 Mementopublic Memento saveToMemento() {return new Memento(vit,def);}//从备忘录恢复 GameRole 的状态public void restoreFromMemento(Memento memento) {this.vit = memento.getVit();this.def = memento.getDef();}//显示public void display() {System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);}
}

备忘录对象

public class Memento {//攻击力private int vit;//防御力private int def;public Memento(int vit, int def) {this.vit = vit;this.def = def;}public int getVit() {return vit;}public int getDef() {return def;}
}

守护者对象

public class Caretaker {//对于 GameRole 如果只保存一次状态private Memento memento;//对于 GameRole 如果保存多次状态
//    private List<Memento> mementos = new ArrayList<>();//对多个游戏角色保存多个状态
//    private HashMap<String, ArrayList<Memento>> rolesMementos = new HashMap<>();public void setMemento(Memento memento) {this.memento = memento;}//取到第 index 个 Originator 的备忘录对象public Memento getMemento() {return memento;}
}

客户端

public class Client {public static void main(String[] args) {//创建游戏角色GameRole gameRole = new GameRole();gameRole.setVit(100);gameRole.setDef(100);System.out.println("和boss大战前的状态");gameRole.display();//把当前状态保存caretakerCaretaker caretaker = new Caretaker();caretaker.setMemento(gameRole.saveToMemento());System.out.println("和boss大战~~~");gameRole.setDef(30);gameRole.setVit(30);gameRole.display();System.out.println("大战后,使用备忘录对象恢复到对战前");gameRole.restoreFromMemento(caretaker.getMemento());System.out.println("恢复后的状态");gameRole.display();}
}

输出

和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到对战前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100

4、总结

  • 备忘录模式给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 适用的场景:后悔药;打游戏时的存档;Windows 里的 ctrl + z;IE 中的后退;数据库的事物管理。
  • 为了节约内存,备忘录模式可以和原型模式配合使用。

八、解释器模式(Interpreter Pattern)

通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求:

  • 先输入表达式的形式,比如 a+b+c-d+e,要求表达式的字母不能重复。
  • 再分别输入 a、b、c、d、e 的值。
  • 最后求出结果:如下图

1、传统方式解决 四则运算问题

编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果。

问题分析:如果加入新的运算符,比如 */ (等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰)

解决方案:可以考虑使用解释器模式,即:表达式->解释器(可以有多种)->结果。

2、解释器模式介绍

基本介绍

在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析树都可以看做是解释器。

解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。

应用场景:

  • 应用可以将一个需要解释执行的句子,表示为一个抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来表达
  • 一个简单语法需要解释的场景

这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等。

原理类图

说明:

  • Context:是环境角色,含有解释器之外的全局信息。
  • AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法被抽象语法树中所有节点共享。
  • TerminalExpression:为终结符表达式,实现与文法中的终结符相关的解释操作
  • NonTerminalExpression:为非终结符表达式,为文法中的非终结符实现的解释操作
  • 说明:输入信息通过 Client 输入即可。

3、解释器模式解决 四则运算问题

原理类图

代码实现

解释器类

/*** 抽象类表达式,通过 HashMap 键值对,可以获取到变量的值*/
public abstract class Expression {/*** a + b - c* 解释公式和数值,key 就是参数[a,b,c], value 就是具体值*/public abstract int interpreter(Map<String, Integer> var);
}/*** 变量的解释器*/
public class VarExpression extends Expression {private String key;//key=a,key=b,key=cpublic VarExpression(String key) {this.key = key;}//interpreter 根据变量名称,返回相应的值@Overridepublic int interpreter(Map<String, Integer> var) {return var.get(key);}
}/*** 加法解释器*/
public class AddExpression extends SymbolExpression {public AddExpression(Expression left, Expression right) {super(left, right);}//处理相加,var仍然是 {a=10.b=20}@Overridepublic int interpreter(Map<String, Integer> var) {return super.left.interpreter(var) + super.right.interpreter(var);}
}/*** 减法解释器*/
public class SubExpression extends SymbolExpression {public SubExpression(Expression left, Expression right) {super(left, right);}//处理相减,var仍然是 {a=10.b=20}@Overridepublic int interpreter(Map<String, Integer> var) {return super.left.interpreter(var) - super.right.interpreter(var);}
}

Context类

public class Calculator {//定义表达式private Expression expression;//构造器函数,传参并解析//expStr = a+bpublic Calculator(String expStr) {//安排运算先后顺序Stack<Expression> stack = new Stack<Expression>();//表达式差分成字符数组 [a,+,b]char[] charArr = expStr.toCharArray();Expression left;Expression right;//遍历我们的字符数组,即遍历 [a,+,b]//针对不同的情况做处理for (int i=0;i<charArr.length;i++) {switch (charArr[i]) {case '+': //当前是 + 号//取出前一个值left = stack.pop();//取出后一个值right = new VarExpression(String.valueOf(charArr[++i]));stack.push(new AddExpression(left,right));break;case '-'://当前是 - 号//取出前一个值left = stack.pop();//取出后一个值right = new VarExpression(String.valueOf(charArr[++i]));stack.push(new SubExpression(left,right));break;default://不是运算符,则表示是数值,创建 VarExpression 对象,并 push 到stackstack.push(new VarExpression(String.valueOf(charArr[i])));break;}}//当遍历完整个 charArray 数组后,stack 就得到最后的 Expression//这个expression是嵌套的this.expression = stack.pop();}public int run(Map<String, Integer> var) {//最后将表达式和var绑定,比如{a=10,b=20}//然后传递给 expression的 interpreter 进行解释执行return this.expression.interpreter(var);}
}

单步调试跟踪到的嵌套的 expression  (expStr=a+b-c+d)

客户端类

public class Client {public static void main(String[] args) throws IOException {String expStr = getExpStr();Map<String, Integer> map = getValue(expStr);Calculator calculator = new Calculator(expStr);int result = calculator.run(map);System.out.println("运算结果:" + expStr + " = " +result);}//获得表达式public static String getExpStr() throws IOException {System.out.print("请输入表达式:");String expStr = new BufferedReader(new InputStreamReader(System.in)).readLine();return expStr;}//获得值映射public static Map<String, Integer> getValue(String expStr){Map<String, Integer> map = new HashMap<String, Integer>();for (char ch:expStr.toCharArray()) {if ((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')) {System.out.print("请输入" + ch + "的值:");Scanner scanner = new Scanner(System.in);String var = scanner.nextLine();map.put(String.valueOf(ch), Integer.valueOf(var));}}return map;}
}

输出:

请输入表达式:a+b-c+d
请输入a的值:10
请输入b的值:1
请输入c的值:3
请输入d的值:1
运算结果:a+b-c+d = 9

4、解释器模式在Spring 框架应用的源码分析

Spring框架中的 SpelExpressionParser  就使用到了解释器模式。

首先要引入 spring-expression 的依赖

        <dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>4.3.7.RELEASE</version></dependency>

测试类

public class SpringExpressionDebugDemo {public static void main(String[] args) {SpelExpressionParser parser = new SpelExpressionParser();Expression expression = parser.parseExpression("100*(2+1)*1+66");Object value = expression.getValue();System.out.println(value);}
}

类图如下

说明:

  • Expression 接口,是解释器的接口,下面有不同的实现类,比如 SpelExpression、CompositeStringExpression。
  • 在我们使用的时候,会根据不同的 Parse 对象,返回不同的 Expression 对象 ( parser.parseExpression() )。

5、解释器模式总结

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树。此时,就可以考虑使用解释器模式,让程序具有良好的扩展性。
  • 应用场景:编译器、运算表达式计算、正则表达式、机器人等。
  • 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低。

九、状态模式(State Pattern)

1、需求 - app抽奖活动

请编写程序完成APP抽奖活动,具体要求如下:

  • 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
  • 奖品数量固定,抽完就不能抽奖
  • 活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
  • 活动的四个状态转换关系图

2、状态模式介绍

基本介绍

状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以互相转换。

当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

原理类图

说明:

  • Context 类为环境角色,用于维护 State 实例,这个实例定义当前状态(聚合 State)
  • State 是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为。
  • ConcreteState 是具体的状态角色,每个子类实现一个与Context的一个状态相关行为。

3、状态模式解决 App 抽奖问题

说明:

  • 定义出一个接口叫状态接口,每个状态都实现它。
  • 接口有扣除积分、抽奖方法、发放奖品方法。

代码实现

抽象状态及其子类

/*** 状态抽象类*/
public abstract class State {//扣除积分public abstract void deductMoney();//是否抽中奖品public abstract boolean raffle();//发放奖品public abstract void dispensePrize();
}/*** 不能抽奖的状态*/
public class NoRaffleState extends State {private Activity activity;// 初始化时传入活动引用,扣除积分后改变其状态public NoRaffleState(Activity activity) {this.activity = activity;}@Overridepublic void deductMoney() {System.out.println("扣除50积分成功,您可以抽奖了");activity.setState(activity.getCanRaffleState());}@Overridepublic boolean raffle() {throw new RuntimeException("扣了积分才能抽奖喔!");}@Overridepublic void dispensePrize() {throw new RuntimeException("不能发放奖品");}
}/*** 可以抽奖的状态*/
public class CanRaffleState extends State{Activity activity;public CanRaffleState(Activity activity) {this.activity = activity;}@Overridepublic void deductMoney() {throw new RuntimeException("已经扣除过了积分,不能在扣除");}@Overridepublic boolean raffle() {System.out.println("正在抽奖,请稍候!");Random r = new Random();int num = r.nextInt(10);//10%的中奖几率if (num == 0) {//改变状态为发布奖品activity.setState(activity.getDispenseState());return true;} else {System.out.println("很遗憾没有抽中奖");//改变状态为不能抽奖activity.setState(activity.getNoRaffleState());return false;}}@Overridepublic void dispensePrize() {throw new RuntimeException("没中奖不能发放奖品");}
}/*** 发放奖品的状态*/
public class DispenseState extends State {private Activity activity;public DispenseState(Activity activity) {this.activity = activity;}@Overridepublic void deductMoney() {throw new RuntimeException("不能扣除积分");}@Overridepublic boolean raffle() {throw new RuntimeException("不能抽奖");}@Overridepublic void dispensePrize() {if (activity.getCount() <= 0) {System.out.println("很遗憾,奖品发放玩了");activity.setState(activity.getDispenseOutState());//退出程序System.exit(0);return;}System.out.println("恭喜,中奖了");// 奖品发放完毕后,改变状态为不能抽奖activity.setState(activity.getNoRaffleState());}
}/*** 奖品发放完毕状态* 说明,当我们 Activity 改变成 DispenseOutState,抽奖活动结束*/
public class DispenseOutState extends State {private Activity activity;public DispenseOutState(Activity activity) {this.activity = activity;}@Overridepublic void deductMoney() {throw new RuntimeException("奖品发送完了,请下次再参加");}@Overridepublic boolean raffle() {throw new RuntimeException("奖品发送完了,请下次再参加");}@Overridepublic void dispensePrize() {throw new RuntimeException("奖品发送完了,请下次再参加");}
}

Context-环境角色类

/*** 抽奖活动*/
public class Activity extends State {//表示当前的状态,是变化的State state = null;//奖品数量int count = 0;//四个属性,表示四种状态State noRaffleState = new NoRaffleState(this);State canRaffleState = new CanRaffleState(this);State dispenseState = new DispenseState(this);State dispenseOutState = new DispenseOutState(this);//构造器//1、初始化当前的状态为 NoRaffleState(即不能抽奖的状态)//2、初始化奖品的数量public Activity(int count) {this.count = count;this.state = noRaffleState;}//扣分, 调用当前状态的 deductMoney@Overridepublic void deductMoney() {state.deductMoney();}@Overridepublic boolean raffle() {throw new RuntimeException("Activity中 该方法不能被调用");}//抽奖@Overridepublic void dispensePrize() {// 如果当前的状态是抽奖成功if (state.raffle()) {//领取奖品state.dispensePrize();}}public void setState(State state) {this.state = state;}public int getCount() {//这里请大家注意,每领取一次奖品,count--int curCount = this.count;this.count--;return curCount;}public State getNoRaffleState() {return noRaffleState;}public State getCanRaffleState() {return canRaffleState;}public State getDispenseState() {return dispenseState;}public State getDispenseOutState() {return dispenseOutState;}
}

Client 客户端

public class Client {public static void main(String[] args) {// 创建活动对象,奖品有1个奖品Activity activity = new Activity(1);for (int i=0;i<100;i++) {System.out.println("--------第" + (i + 1) + "次抽奖----------");//参加抽奖,第一步扣除积分activity.deductMoney();//第二部抽奖activity.dispensePrize();}}
}

其中一次的输出结果

--------第1次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
很遗憾没有抽中奖
--------第2次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
很遗憾没有抽中奖
--------第3次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
很遗憾没有抽中奖
--------第4次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
很遗憾没有抽中奖
--------第5次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
恭喜,中奖了
--------第6次抽奖----------
扣除50积分成功,您可以抽奖了
正在抽奖,请稍候!
很遗憾,奖品发放玩了Process finished with exit code 0

4、状态模式在实际项目中的使用——借贷平台源码剖析

借贷平台的订单有审核-发布-抢单 等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式。

传统方式

通常通过 if-else 判断订单的状态,从而实现不同的逻辑,伪代码如下:

if(审核){//审核逻辑
} else if(发布){//发布逻辑
} else if(接单){//接单逻辑
}

问题分析 :这类代码难以应对变化,在添加一种状态时,我们需要手动添加 if-else ,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的bug,难以维护。

使用状态模式完成 借贷平台项目的审核模块

订单流程的状态图

设计的大致类图

State 接口,定义了各个行为

public interface State {/*** 电审*/void checkEvent(Context context);/*** 电审失败*/void checkFailEvent(Context context);/*** 定价发布*/void makePriceEvent(Context context);/*** 接单*/void acceptOrderEvent(Context context);/*** 无人接单失效*/void noPeopleAcceptOrderEvent(Context context);/*** 付款*/void payOrderEvent(Context context);/*** 接单人支付失败*/void orderFailEvent(Context context);/*** 反馈*/void feedBackEvent(Context context);/*** 获取当前状态信息*/String getCurrentState();
}

AbstractState - 空实现接口的所有方法

/*** 抽象类,默认实现了State接口的所有方法* 该类的所有方法,其子类可以有选择的实现*/
public class AbstractState implements State {protected static final RuntimeException EXCEPTION = new RuntimeException();@Overridepublic void checkEvent(Context context) {throw EXCEPTION;}@Overridepublic void checkFailEvent(Context context) {throw EXCEPTION;}@Overridepublic void makePriceEvent(Context context) {throw EXCEPTION;}@Overridepublic void acceptOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void noPeopleAcceptOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void payOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void orderFailEvent(Context context) {throw EXCEPTION;}@Overridepublic void feedBackEvent(Context context) {throw EXCEPTION;}@Overridepublic String getCurrentState() {throw EXCEPTION;}
}

各个具体的状态,根据流程图的逻辑来进行状态变化

public class GenerateState extends AbstractState {@Overridepublic void checkEvent(Context context) {context.setState(new ReviewState());}@Overridepublic void checkFailEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return "通用状态";}
}public class ReviewState extends AbstractState {@Overridepublic void makePriceEvent(Context context) {context.setState(new PublishState());}@Overridepublic String getCurrentState() {return "审核状态";}
}public class PublishState extends AbstractState {@Overridepublic void acceptOrderEvent(Context context) {context.setState(new NotPayState());}@Overridepublic void noPeopleAcceptOrderEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return "发布状态";}
}public class NotPayState extends AbstractState {@Overridepublic void payOrderEvent(Context context) {context.setState(new PaidState());}@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return "未支付状态";}
}public class PaidState extends AbstractState {@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return "已支付状态";}
}public class FeedBackState extends AbstractState {@Overridepublic String getCurrentState() {return "已完结状态";}
}

Context 环境角色

//环境上下文,实现 State的所有方法,每次调用后输出当前状态信息
public class Context extends AbstractState {State state = null;//当前状态@Overridepublic void checkEvent(Context context) {state.checkEvent(this);getCurrentState();}@Overridepublic void checkFailEvent(Context context) {state.checkFailEvent(this);getCurrentState();}@Overridepublic void makePriceEvent(Context context) {state.makePriceEvent(this);getCurrentState();}@Overridepublic void acceptOrderEvent(Context context) {state.acceptOrderEvent(this);getCurrentState();}@Overridepublic void noPeopleAcceptOrderEvent(Context context) {state.noPeopleAcceptOrderEvent(this);getCurrentState();}@Overridepublic void payOrderEvent(Context context) {state.payOrderEvent(this);getCurrentState();}@Overridepublic void orderFailEvent(Context context) {state.orderFailEvent(this);getCurrentState();}@Overridepublic void feedBackEvent(Context context) {state.feedBackEvent(this);getCurrentState();}public State getState() {return state;}public void setState(State state) {this.state = state;}@Overridepublic String getCurrentState() {System.out.println("当前状态:" + state.getCurrentState());return state.getCurrentState();}
}

客户端

public class Client {public static void main(String[] args) {//创建 Context 对象Context context = new Context();//将当前状态设置为 PublishState 状态context.setState(new PublishState());context.getCurrentState();//输出当前状态context.acceptOrderEvent(context);//接收订单context.payOrderEvent(context);//付款context.feedBackEvent(context);//完结}
}

输出

当前状态:发布状态
当前状态:未支付状态
当前状态:已支付状态
当前状态:已完结状态

5、状态模式总结

代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中。

方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错。

符合开闭原则、容易增删状态。

会产生很多类,每个状态都要有一个对应的类,当状态过多时会产生很多类,加大维护难度。

应用场景:当一个事件或者对象有很多种状态,状态之间会互相转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。

十、策略模式(Strategy Pattern)

1、需求 - 编写鸭子项目

1、有各种鸭子(比如野鸭、北京鸭、水鸭等,鸭子有各种行为,比如叫、飞行等)

2、显示鸭子的信息

2、传统方案解决鸭子项目

代码实现

public abstract class Duck {//显示鸭子信息public abstract void display();public void quack() {System.out.println("鸭子嘎嘎叫");}public void swim() {System.out.println("鸭子会游泳~~");}public void fly() {System.out.println("鸭子会飞~~");}
}public class WildDuck extends Duck {@Overridepublic void display() {System.out.println("~~~野鸭~~~");}
}public class BeijingDuck extends Duck {@Overridepublic void display() {System.out.println("~~~北京鸭~~~");}//因为北京鸭不能飞翔,因此需要重写fly@Overridepublic void fly() {System.out.println("北京鸭不能飞翔");}
}public class ToyDuck extends Duck {@Overridepublic void display() {System.out.println("~~~玩具鸭~~~");}//需要重写父类的所有方法@Overridepublic void quack() {System.out.println("玩具鸭不能叫~~~");}@Overridepublic void swim() {System.out.println("玩具鸭不会游泳~~~");}@Overridepublic void fly() {System.out.println("玩具鸭不会飞翔~~~");}
}

问题分析

  1. 其他鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞,这是不正确的。

  2. 上面说的问题1,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应。

  3. 为了改进1问题,我们可以通过 覆盖 fly 方法解决。

  4. 问题又来了,如果我们有一个玩具鸭子ToyDuck,它不会游泳,也不会飞,不需要这两个方法,这样就需要 ToyDuck 去覆盖 Duck 的所有实现方法。

3、策略模式介绍

基本介绍

策略模式(Strategy)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法独立于使用算法的客户。

这算法体现了几个设计原则:

  • 第一、把变化的代码从不变的代码中分离出来;
  • 第二、针对接口编程而不是具体类(定义了策略接口);
  • 第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

原理类图

说明:从上图可以看到,context 有成员变量 strategy 或其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定。

4、策略模式解决鸭子问题

编写程序完成前面的鸭子项目,要求使用策略模式。

思路分析:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者。

策略接口 - 飞翔

public interface FlyBehavior {void fly();
}public class GoodFlyBehavior implements FlyBehavior {@Overridepublic void fly() {System.out.println(" 飞翔技术高超 ");}
}public class BadFlyBehavior implements FlyBehavior {@Overridepublic void fly() {System.out.println(" 飞翔技术一般 ");}
}public class NoFlyBehavior implements FlyBehavior {@Overridepublic void fly() {System.out.println(" 不会飞翔 ");}
}

策略接口-叫声

public interface QuackBehavior {void quack();
}public class GeGeQuackBehavior implements QuackBehavior {@Overridepublic void quack() {System.out.println(" 咯咯叫 ");}
}public class GagaQuackBehavior implements QuackBehavior {@Overridepublic void quack() {System.out.println(" 嘎嘎叫 ");}
}public class NoQuackBehavior implements QuackBehavior {@Overridepublic void quack() {System.out.println(" 不会叫 ");}
}

context,聚合各个策略接口 - 鸭子,包括其子类

public abstract class Duck {//飞翔属性,策略接口protected FlyBehavior flyBehavior;//叫声属性,策略接口protected QuackBehavior quackBehavior;public Duck() {}public void setFlyBehavior(FlyBehavior flyBehavior) {this.flyBehavior = flyBehavior;}public void setQuackBehavior(QuackBehavior quackBehavior) {this.quackBehavior = quackBehavior;}//显示鸭子信息public abstract void display();public void quack() {if (quackBehavior != null) {quackBehavior.quack();}}public void swim() {System.out.println("鸭子会游泳~~");}public void fly() {if (flyBehavior != null) {flyBehavior.fly();}}
}public class WildDuck extends Duck {//构造器,传入 FlyBehavior 的对象public WildDuck() {super();flyBehavior = new GoodFlyBehavior();quackBehavior = new GagaQuackBehavior();}@Overridepublic void display() {System.out.println("~~~野鸭~~~");}
}public class BeijingDuck extends Duck {public BeijingDuck() {super();flyBehavior = new NoFlyBehavior();quackBehavior = new GeGeQuackBehavior();}@Overridepublic void display() {System.out.println("~~~北京鸭~~~");}
}public class ToyDuck extends Duck {public ToyDuck() {super();flyBehavior = new NoFlyBehavior();quackBehavior = new NoQuackBehavior();}@Overridepublic void display() {System.out.println("~~~玩具鸭~~~");}
}

Client 客户端

public class Client {public static void main(String[] args) {WildDuck wildDuck = new WildDuck();wildDuck.display();wildDuck.fly();wildDuck.quack();ToyDuck toyDuck = new ToyDuck();toyDuck.display();toyDuck.fly();toyDuck.quack();BeijingDuck beijingDuck = new BeijingDuck();beijingDuck.display();beijingDuck.fly();beijingDuck.quack();System.out.println("北京鸭实际飞翔能力");beijingDuck.setFlyBehavior(new BadFlyBehavior());beijingDuck.fly();}
}

5、策略模式在 JDK-Arrays 应用的源码分析

Arrays 的 Comparator 使用了策略模式。

如下Demo:

public class Strategy {public static void main(String[] args) {Integer[] data = {9,1,2,8,4,3};Comparator<Integer> comparator = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {if (o1 > o2) {return 1;} else {return -1;}}};//Arrays.sort(data, comparator);System.out.println(Arrays.toString(data));}
}

说明:

  • Comparator 是策略接口
  • 匿名类对象 new Comparator<Integer>() {} ,实现了 Comparator 接口
  • public int compare(Integer o1, Integer o2)  函数:指定具体的处理方式

再看下 Arrays.sort() 的源码

    public static <T> void sort(T[] a, Comparator<? super T> c) {if (c == null) {sort(a);  //默认方式} else {if (LegacyMergeSort.userRequested)legacyMergeSort(a, c); //使用策略对象celseTimSort.sort(a, 0, a.length, c, null, 0, 0); //使用策略对象c}}

此时如果需要实现降序排序,则只要修改 匿名类对象的 compare() 即可,如下

        Comparator<Integer> comparator = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {if (o1 > o2) {return -1;} else {return 1;}}};

6、策略模式总结

1、策略模式的关键是:分析项目中变化部分于不变部分。

2、策略模式的核心思想是:多用聚合/组合,少用继承;用行为类组合,而不是行为的继承。

3、体现了对“修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if-else if-else)

4、提供了可以替换继承关系的方法:策略模式将算法封装在独立的Strategy类中,使得你可以独立于其Context改变它,使他易于切换、易于理解、易于扩展。

5、需要注意的是:没添加一个策略就要增加一个类,当策略过多时会导致类数目庞大。

十一、责任链模式(职责链模式)(Chain of Responsibility Pattern)

1、需求 - 学校OA系统的采购审批

采购员采购教学器材

  1. 如果金额 <= 5000,由教学主任审批 (0<x<=5000)
  2. 如果金额 <= 10000,由院长审批 (5000<x<=10000)
  3. 如果金额 <= 30000,由副校长审批 (10000<x<=30000)
  4. 如果金额  >30000,由院长审批 (30000<x)

请设计程序完成采购审批需求。

2、传统方案解决 OA系统审批

问题:

  • 传统方式是:接收到一个请求后,根据采购金额来调用对应的 Approver(审批人),完成审批。
  • 客户端这里会使用到分支判断来对不同的采购请求进行处理,这样就存在如下问题

1)如果各个级别的人员审批金额发生变化,在客户端也需要修改。

2)客户端必须明确的知道有多少个审批级别和访问。

  • 这样的话,对一个采购请求进行处理和审批人就存在强耦合关系,不利于代码扩展和维护。
  • 解决方案 -> 职责链模式

3、职责链模式介绍

基本介绍

职责链模式(Chain Responsiblity Pattern),又叫责任链模式,为请求创建了一个接收者对象的链。这种模式对请求的发送和接收进行解耦。

职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理请求,那么它会把相同的请求传给下一个接收者,依次类推。

这种类型的设计模式属于行为模式。

原理类图

说明:

  • Handler:抽象的处理者,定义了一个处理请求的接口,同时含有另外的Handler。
  • ConcreteHandlerA/B 是具体的处理者,处理它自己的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将请求交给后继者去处理,从而形成一个职责链。
  • Request ,表示一个请求,含有很多属性。

4、职责链模式解决 OA系统审批

代码实现

请求类

public class PurchaseRequest {private int id;private int type;private float price;public PurchaseRequest(int id, int type, float price) {this.id = id;this.type = type;this.price = price;}public int getId() {return id;}public int getType() {return type;}public float getPrice() {return price;}
}

抽象处理者

public abstract class Approver {Approver approver;String name;public Approver(String name) {this.name = name;}public void setApprover(Approver approver) {this.approver = approver;}//处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象public abstract void processRequest(PurchaseRequest request);
}

具体处理者

public class DepartmentApprover extends Approver {public DepartmentApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getPrice() <= 5000) {System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(request);}}
}public class CollegeApprover extends Approver {public CollegeApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getPrice() > 5000 && request.getPrice() <= 10000) {System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(request);}}
}public class ViceSchoolApprover extends Approver {public ViceSchoolApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getPrice() > 10000 && request.getPrice() <= 30000) {System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(request);}}
}public class SchoolApprover extends Approver {public SchoolApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getPrice() > 30000) {System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(request);}}
}

客户端

public class Client {public static void main(String[] args) {// TODO Auto-generated method stub//创建一个请求PurchaseRequest purchaseRequest = new PurchaseRequest(1, 1, 111000);//创建相关的审批人DepartmentApprover departmentApprover = new DepartmentApprover("张主任");CollegeApprover collegeApprover = new CollegeApprover("李院长");ViceSchoolApprover viceSchoolApprover = new ViceSchoolApprover("王副校");SchoolApprover schoolApprover = new SchoolApprover("佟校长");//需要将各个审批级别的下一个设置好 (处理人构成环形: )departmentApprover.setApprover(collegeApprover);collegeApprover.setApprover(viceSchoolApprover);viceSchoolApprover.setApprover(schoolApprover);schoolApprover.setApprover(departmentApprover);departmentApprover.processRequest(purchaseRequest);viceSchoolApprover.processRequest(purchaseRequest);}
}

5、职责链模式在 SpringMVC 框架应用的源码分析

SpringMVC 的 HandlerExecutionChain  类就使用到职责模式

//SpringMVC 的核心处理类
public class DispatcherServlet extends FrameworkServlet {protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//声明HandlerExecutionChain对象HandlerExecutionChain mappedHandler = null;...//获取到HandlerExecutionChain 对象mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}...//在mappedHandler.applyPreHandle 内部得到了HandlerInterceptor interceptor//调用了拦截器的interceptor.preHandleif (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}...//说明:mappedHandler.applyPostHandle 方法内部获取到拦截器,并调用//拦截器的interceptor.postHandle(request, response, this.handler, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}}//在mappedHandler.applyPreHandle 内部中,
//还调用了triggerAfterCompletion 方法,该方法中调用了
//HandlerInterceptor interceptor = getInterceptors()[i];
public class HandlerExecutionChain {boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}}
}

说明:

  • SpringMVC 请求流程中,执行了拦截器相关方法 interceptor.preHandler、  interceptor.postHandler 等等。
  • SpringMVC 请求中,使用到职责链模式,还使用了适配器模式。
  • HandlerExecutionChain 主要负责的是拦截器的执行和请求,但他本身不处理请求,只是将请求分配给链上注册的处理器执行,这是职责链实现方式,减少了职责链本身与处理逻辑之间的耦合,规范了处理流程。
  • HandlerExecutionChain 维护了 HandlerInterceptor的集合,可以向其中注册相应的拦截器。

6、职责链模式总结

1、将请求和处理分开,实现解耦,提高系统的灵活性。

2、简化了对象,使对象不需要知道链的结构。

3、性能会受到影响,特别是在链比较长的时候,因此需要控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext() 方法终判断是否已经超过阈值,超过则不允许该链建立。

4、调式不方便。采用了类似递归的方式,调试时逻辑可能比较复杂。

5、最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中Tomcat对Encoding的处理、拦截器。

图解Java设计模式学习笔记——行为型模式(模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式)相关推荐

  1. 图解Java设计模式学习笔记——结构型模式(适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式、代理模式)

    一.适配器模式(类适配器.对象适配器.接口适配器) 1.现实生活中的例子 泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了国内的电器了. 2.基本介绍 适配器模式(Ad ...

  2. java 设计模式学习笔记十四 template模版模式

    /**  * 模版  *   * @time 下午09:58:00  * @author retacn yue  * @Email zhenhuayue@sina.com  */ public abs ...

  3. java设计模式学习笔记之装饰模式

    java设计模式学习笔记之装饰模式 尊重原创,转载请注明出处,原文地址: http://blog.csdn.net/qq137722697 这是一个使用策略模式和构建模式设计的网络请求框架,去看看吧& ...

  4. java/android 设计模式学习笔记(3)---工厂方法模式

    这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或 ...

  5. java设计模式-学习笔记

    java设计模式 概述 "设计模式"这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中. <设计模式:可复用面向对象软件的基础>(Design Patter ...

  6. java 设计模式学习笔记十 bridge桥模式

    bridge桥模式 将抽象和行为划分开来,各自独立但能动态结合 抽象的接口 /**  * 咖啡抽象类  *   * @time 下午09:14:27  * @author retacn yue  * ...

  7. Java设计模式学习笔记:单例模式(一)

    今天学到单例模式,对几种单例模式的特点做了一次梳理,从线程安全性和性能两个方面来说. 首先都知道有两种最常见的单例模式:饿汉式和懒汉式,如下: 饿汉式: public class Hangry() { ...

  8. java反射学习笔记(常用的一些方法)

    User类(用于测试) package com.qingfeng.springbootstudy.entity;import java.io.Serializable;/*** (User)实体类** ...

  9. 《Effective Java》学习笔记 - (1) 使用静态工厂方法代替构造器

    文章目录 前言 使用静态工厂方法代替构造器 1. 优点 1.1 静态工厂方法有名称 1.2 不必每次调用的时候都创建一个对象 1.3 可以返回类型的任何子类型的对象 1.4 所返回的对象的类型可以随着 ...

最新文章

  1. Ubuntu 下搭建 NFS 服务
  2. 条件查询_SQL简单查询(条件查询 模糊查询)
  3. Linux操作系统六大优点
  4. Orleans解决并发之痛(五):Web API
  5. android8 老手机,华为多款老旧手机获升安卓8.0,流畅度飙升!
  6. STM32移植LWIP
  7. 原生python自带的ide_python自带的IDE是一个功能强大的IDE
  8. 基于JAVA+SpringMVC+Mybatis+MYSQL的校园订餐点餐外卖管理系统
  9. replaceFirst、replaceAll、replace区别
  10. sublime3安装常用插件
  11. 基于STC89C52的小车制作上篇,用电机将小车驱动起来之对L298N逻辑输入N1~N4详解
  12. 985高校硕导跳槽高中当老师,博士扎堆中小学,是内卷还是进步?
  13. html设置只在最后一页显示页脚,word文档只在最后一页插入页眉怎么操作
  14. Ajax传参里面含有特殊字符
  15. 荐书 | 22本颠覆我们认知的思维方式(上)
  16. 每天一种设计模式之抽象工厂模式(Java实现)
  17. unity3d 地面印花_unity冬季场景地面地形白雪纹理材质贴图游戏素材Winter Ground Pack v1.1...
  18. 轻轻松松背单词软件测试,完美单词王app
  19. PDF文件怎么拆分?看完就会了!
  20. 使用VBA实现数据统计

热门文章

  1. 非监督特征学习与深度学习(十四)--------循环神经网络
  2. gui实现2048小游戏
  3. Ring Buffer 的应用
  4. 7-46 新浪微博热门话题
  5. Arduino Mega2560 PWM
  6. python学习_循环语句
  7. 离职后工作居住证如何办理延期注销
  8. 安防RTSP协议摄像头实现WEB端无插件直播流媒体服务EasyNVR实现海康大华宇视摄像头网页播放的方法
  9. 私域流量运营平台有哪些?
  10. 灰色预测模型_python