在软件工程中, 行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。
模板方法模式
应用案例 - 豆浆制作问题
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
- 请使用 模板方法模式 完成 (说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式 )
模板方法模式基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
- 简单说,模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
- 这种类型的设计模式属于行为型模式。
模板方法模式原理类图
UML类图
对原理类图的说明-即(模板方法模式的角色及职责)
- AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法operationr2,3,4
- ConcreteClass 实现抽象方法operationr2,3,4, 以完成算法中特点子类的步骤
解决豆浆制作问题(模板方法模式)
UML类图
代码示例
//抽象类表示豆浆
public abstract class SoyaMilk {
//用于记录步数
public int i = 1;
/**制作 模板方法 make(),模板方法可以做成final,不让子类去覆盖 调用步骤全交给模板方法*/
public final void make(){
select();
addCondiments();
soak();
beat();
};
/**选材料*/
private void select(){
System.out.println("第"+(i++)+"步:选择好的新鲜黄豆");
};
/**添加不同的配料 抽象方法,不同豆浆不同实现 子类具体实现*/
public abstract void addCondiments();
/**侵泡*/
private void soak(){
System.out.println("第"+(i++)+"步:黄豆和配料开始侵泡");
};
/**打浆*/
private void beat(){
System.out.println("第"+(i++)+"步:黄豆和配料放入豆浆机开始打浆");
};
}
//-----------------------------------------------------------------------------
//具体子类实现
public class BlackBeanSoyaMilk extends SoyaMilk {
@OVerride
public void addCondiments() {
System.out.println("第"+(i++)+"步:准备上好的黑豆配料");
}
}
public class PeanutSoyaMilk extends SoyaMilk {
@OVerride
public void addCondiments() {
System.out.println("第"+(i++)+"步:准备上好的花生配料");
}
}
//-----------------------------------------------------------------------------
//测试
public class Client {
public static void main(String[] args) {
System.out.println("---------制作花生豆浆---------");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("---------制作黑豆豆浆---------");
SoyaMilk blackBeanSoyaMilk = new BlackBeanSoyaMilk();
blackBeanSoyaMilk.make();
}
}
模板方法模式的钩子方法
说明
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以 视情况要不要覆盖它,该方法称为“钩子”。
- 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配 料,请使用钩子方法对前面的模板方法进行改造
代码示例
//抽象类表示豆浆
public abstract class SoyaMilk {
//用于记录步数
public int i = 1;
/**制作 模板方法 make,模板方法可以做成final,不让子类去覆盖*/
public final void make(){
select();
//调用钩子方法判断是否需要添加调料
if (customerWantCondiments()){
addCondiments();
}
soak();
beat();
};
/**选材料*/
private void select(){
System.out.println("第"+(i++)+"步:选择好的新鲜黄豆");
};
/**添加不同的配料 抽象方法,不同豆浆不同实现 子类具体实现*/
public abstract void addCondiments();
/**侵泡*/
private void soak(){
System.out.println("第"+(i++)+"步:黄豆和配料开始侵泡");
};
/**打浆*/
private void beat(){
System.out.println("第"+(i++)+"步:黄豆和配料放入豆浆机开始打浆");
};
/**钩子方法 决定是否添加配料 默认添加*/
boolean customerWantCondiments(){
return true;
}
}
//-----------------------------------------------------------------------------
//具体子类实现
public class BlackBeanSoyaMilk extends SoyaMilk {
@Override
public void addCondiments() {
System.out.println("第"+(i++)+"步:准备上好的黑豆配料");
}
}
public class PeanutSoyaMilk extends SoyaMilk {
@Override
public void addCondiments() {
System.out.println("第"+(i++)+"步:准备上好的花生配料");
}
}
//新增测试类 纯豆浆 不加入任何配料
public class PureSoyaMilk extends SoyaMilk {
//添加调料的方法默认实现(空方法)
@Override
public void addCondiments() {}
//重写钩子方法 返回false 设置不添加调料
@Override
boolean customerWantCondiments() {
return false;
}
}
//-----------------------------------------------------------------------------
//测试
public class Client {
public static void main(String[] args) {
System.out.println("---------制作花生豆浆---------");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("---------制作黑豆豆浆---------");
SoyaMilk blackBeanSoyaMilk = new BlackBeanSoyaMilk();
blackBeanSoyaMilk.make();
System.out.println("---------制作纯豆浆---------");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
模板方法模式在Spring框架应用的源码分析
Spring IOC容器初始化时运用到的模板方法模式
UML类图
源码截图
ConfigurableApplicationContext 最高层接口
AbstractApplicationContext最高实现模板方法的父类
类似前面的SoyaMilk,豆浆类抽象类
GenericApplicationContext具体的功能实现实例类,其他子类
模板方法模式的注意事项和细节
- 基本思想是:**算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改 **
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不 变,同时由子类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加, 使得系统更加庞大
- 一般模板方法都加上final关键字, 防止子类重写模板方法.
- 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模 式来处理
命令模式
应用案例 - 智能生活项目需求
看一个具体的需求
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
- 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口 给app调用,这时 就可以考虑使用命令模式。
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
- 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品
命令模式基本介绍
- 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
- 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
- Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
命令模式的原理类图
UML类图
对原理类图的说明-即(命名模式的角色及职责)
- Invoker 是调用者角色
- Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
命令模式解决智能生活项目
UML类图
代码示例
//命令接口/抽象层
public interface Command {
/**执行动作(操作)*/
void execute();
/**撤销动作(某个操作)*/
void undo();
}
//-----------------------------------------------------------------------------
//具体功能实现 电灯 (命令接收者)
public class LightReceiver {
public void on(){
System.out.println("电灯打开了...");
}
public void off(){
System.out.println("电灯关闭了...");
}
}
//扩展 电视机
public class TVReceiver {
public void on(){
System.out.println("电视打开了...");
}
public void off(){
System.out.println("电视关闭了...");
}
}
//-----------------------------------------------------------------------------
//电灯打开命令类 具体命令实现类 (命令实体)
//电灯
public class LightOnCommand implements Command {
/**聚合LightReceiver*/
LightReceiver light;
public LightOnCommand(LightReceiver light) {
this.light = light;
}
@Override
public void execute() {
light.on();//调用接收者的方法
}
@Override
public void undo() {
light.off();//调用接收者的方法
}
}
public class LightOffCommand implements Command{
/**聚合LightReceiver*/
LightReceiver light;
public LightOffCommand(LightReceiver light) {
this.light = light;
}
@Override
public void execute() {
light.off();//调用接收者的方法
}
@Override
public void undo() {
light.on();//调用接收者的方法
}
}
//------------------------------------
/*空类 空命令 没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,
对象什么都不做 其实也是一种设计模式 可以省掉对空的判断*/
public class NoCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
//------------------------------------
//扩展 电视机
public class TVOnCommand implements Command {
/**聚合LightReceiver*/
TVReceiver tv;
public TVOnCommand(TVReceiver tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.on();//调用接收者的方法
}
@Override
public void undo() {
tv.off();//调用接收者的方法
}
}
public class TVOffCommand implements Command {
/**聚合LightReceiver*/
TVReceiver tv;
public TVOffCommand(TVReceiver tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.off();//调用接收者的方法
}
@Override
public void undo() {
tv.on();//调用接收者的方法
}
}
//-----------------------------------------------------------------------------
//遥控器类 (命令发布者)
public class Remoteontroller {
/**开关 按钮的命令数组*/
private Command[] onCommands;
private Command[] offCommands;
/**执行撤销的命令*/
private Command undoCommand;
//构造器
public Remoteontroller() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0;i < 5;i++){
//先设置为空命令
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
/**
* 给我们的按钮设置你需要的命令
* @param no 命令在数组中的下标位置
* @param onCommand 开命令实现对象
* @param offCommand 开命令实现对象
*/
public void setCommand(int no,Command onCommand,Command offCommand){
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
/**
* 按下开按钮
* @param no 命令在数组中的下标位置
*/
public void onButtonWasPushed(int no){
onCommands[no].execute();
//记录这次的操作
undoCommand = onCommands[no];
}
/**
* 按下关按钮
* @param 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();
//创建电灯开关的命令对象(命令)
Command lightOnCommand = new LightOnCommand(lightReceiver);
Command lightOffCommand = new LightOffCommand(lightReceiver);
//创建遥控器类(命令发布者)
Remoteontroller remoteontroller = new Remoteontroller();
//给遥控器设置相关命令 比如no = 0 就是对电灯的开关操作
remoteontroller.setCommand(0,lightOnCommand,lightOffCommand);
System.out.println("---------按下灯的开按钮---------");
remoteontroller.onButtonWasPushed(0);
System.out.println("---------按下灯的关按钮---------");
remoteontroller.offButtonWasPushed(0);
System.out.println("---------按下撤销按钮---------");
remoteontroller.undoButtonWasPushed();
System.out.println("===================使用遥控器操作电视机===================");
//创建电灯对象(命令接收者)
TVReceiver tvReceiver = new TVReceiver();
//创建电灯开关的命令对象(命令)
Command tvOnCommand = new TVOnCommand(tvReceiver);
Command tvOffCommand = new TVOffCommand(tvReceiver);
remoteontroller.setCommand(1,tvOnCommand,tvOffCommand);
System.out.println("---------按下TV的开按钮---------");
remoteontroller.onButtonWasPushed(1);
System.out.println("---------按下TV的关按钮---------");
remoteontroller.offButtonWasPushed(1);
System.out.println("---------按下撤销按钮---------");
remoteontroller.undoButtonWasPushed();
}
}
命令模式在Spring框架JdbcTemplate应用的源码分析
流程图
UML类图
源码图 + 角色分析
角色分析:
- StatementCallback 接口 ,类似命令接口(Command)
- class QueryStatementCallback implements StatementCallback, SqlProvider , 匿名内部类,实现了命令接口,同时也充当命令接收者
- 命令调用者是JdbcTemplate , 其中execute(StatementCallback action) 方法中,调用action.doInStatement方法. 不同的实现StatementCallback接口的对象,对应不同的doInStatemnt实现逻辑
- 另外实现StatementCallback 命令接口的子类还有QueryStatementCallback、
命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要 调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说: 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制
访问者模式
应用案例 - 测评系统的需求
完成测评系统需求
-
将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对 该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等)
-
传统方案
-
传统方式的问题分析
- 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改 动较大,违反了ocp原则, 不利于维护
- 扩展性不好,比如 增加了 新的人员类型,或者管理方法,都不好做
- 引出我们会使用新的设计模式 – 访问者模式
访问者模式基本介绍
- **访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 **
- 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作”污染”这些对象的类,可以选用访问者模式解决
访问者模式的原理类图
访问者模式的原理类图
对原理类图的说明-即(访问者模式的角色及职责)
- Visitor 是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
- ConcreteVisitor :是一个具体的访问值 实现每个有Visitor 声明的操作,是每个操作实现的部分.
- ObjectStructure 能枚举它的元素, 可以提供一个高层的接口,用来允许访问者访问元素
- Element 定义一个accept 方法,接收一个访问者对象
- ConcreteElement 为具体元素,实现了accept 方法
访问者模式应用实例
UML类图
代码示例
//测评结果抽象类 访问者抽象类
public abstract class Action {
/**得到男 的测评结果*/
public abstract void getManResult(Man man);
/**得到女 的测评结果*/
public abstract void getWomanResult(Woman woman);
}
//-----------------------------------------------------------------------------
//具体访问者实现
//成功
public class Success extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男性给该歌手的评价是 很成功!");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女性给该歌手的评价是 很成功!");
}
}
//失败
public class Fail extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男性给该歌手的评价是 失败!");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女性给该歌手的评价是 失败!");
}
}
//扩展 待定
public class Wait extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男性给该歌手的评价是 待定...");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女性给该歌手的评价是 待定...");
}
}
//-----------------------------------------------------------------------------
//访问者方法调用(桥梁类),抽象类 (人抽象类)
public abstract class Person {
/**提供一个方法,让访问者可以访问*/
public abstract void accept(Action action);
}
//具体实现 (男性)
/*
1.这里我们使用到了双分派,即首先在客户端程序中,将具体状态作为参数传递Man中(第一次分派)
2.然后Man类调用作为参数的对象中的 "具体方法" 这里的方法getManResult,同时将自己(this)作为参数
传入,完成第二次的分派
*/
public class Man extends Person {
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
//具体实现 (女性)
public class Woman extends Person {
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
//-----------------------------------------------------------------------------
//数据结构 管理很多人(Man Woman)
public class ObjectStructure {
/**维护了一个集合 很多人的评价*/
private List<Person> persons = new ArrayList<>();
/**增加到list*/
public void attach(Person person){
persons.add(person);
}
/**移除*/
public void detach(Person person){
persons.remove(person);
}
/**显示测评情况*/
public void display(Action action){
for (Person person : persons) {
person.accept(action);
}
}
}
//-----------------------------------------------------------------------------
//客户端
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//成功
Success success = new Success();
objectStructure.display(success);
System.out.println("-------------------");
Fail fail = new Fail();
objectStructure.display(fail);
System.out.println("-------------------");
Wait wait = new Wait();
objectStructure.display(wait);
}
}
小结
- 上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。 双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
- 以上述实例为例,假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码。
缺点分析 + 改进
-
访问者模式解决此案例存在Person子类扩展问题,如果再扩展小孩、老人等等…则需要更改Action抽象类以及每一个实现子类,存在扩展更改太多代码问题
-
另一种设计 一样是双向绑定,并且遵守了依赖倒转原则,迪米特法则,扩展更加方便
代码示例
//测评结果抽象类 访问者抽象类 public abstract class Action { /**测评结果*/ public abstract void getEvaluationResults(Person person); } //----------------------------------------------------------------------------- //具体访问者实现 //成功 public class Success extends Action { @Override public void getEvaluationResults(Person person) { System.out.println(person.getType() + "给该歌手的评价是 很成功!"); } } //失败 public class Fail extends Action { @Override public void getEvaluationResults(Person person) { System.out.println(person.getType() + "给该歌手的评价是 失败!"); } } //扩展 待定 public class Wait extends Action { @Override public void getEvaluationResults(Person person) { System.out.println(person.getType() + "给该歌手的评价是 待定..."); } } //----------------------------------------------------------------------------- //访问者方法调用(桥梁类),抽象类 (人抽象类) public abstract class Person { //设置人群 女、男、小孩... private String type; /**提供一个方法,让访问者可以访问*/ public abstract void accept(Action action); public Person(String type) { this.type = type; } public String getType() { return type; } } //----------------------------------------------------------------------------- //具体实现 (男性) public class Man extends Person { public Man() { super("男性"); } @Override public void accept(Action action) { action.getEvaluationResults(this); } } //具体实现 (女性) public class Woman extends Person { public Woman() { super("女性"); } @Override public void accept(Action action) { action.getEvaluationResults(this); } } //扩展 (小孩) public class Child extends Person { public Child() { super("小孩"); } @Override public void accept(Action action) { action.getEvaluationResults(this); } } //----------------------------------------------------------------------------- //数据结构 管理很多人(Man Woman) public class ObjectStructure { /**维护了一个集合 很多人的评价*/ private List<Person> persons = new ArrayList<>(); /**增加到list*/ public void attach(Person person){ persons.add(person); } /**移除*/ public void detach(Person person){ persons.remove(person); } /**显示测评情况*/ public void display(Action action){ for (Person person : persons) { person.accept(action); } } } //----------------------------------------------------------------------------- //客户端 public class Client { public static void main(String[] args) { //创建ObjectStructure ObjectStructure objectStructure = new ObjectStructure(); objectStructure.attach(new Man()); objectStructure.attach(new Woman()); objectStructure.attach(new Child()); //成功 Success success = new Success(); objectStructure.display(success); System.out.println("-------------------"); Fail fail = new Fail(); objectStructure.display(fail); System.out.println("-------------------"); Wait wait = new Wait(); objectStructure.display(wait); } }
访问者模式的注意事项和细节
- 优点
- 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据 结构相对稳定的系统
- 缺点
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
- 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
- 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.
迭代器模式
应用案例
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系 组成,一个学校有多个学院,一个学院有多个系。如图:
使用传统的设计方案
结构图
传统的方式的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的遍历的操作
- 解决方案:=> 迭代器模式
迭代器模式基本介绍
- 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
- 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
- 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素, 客户端不需要知道集合对象的底层表示,即:不暴露其内部的结构。
迭代器模式的原理类图
原理类图
对原理类图的说明-即(迭代器模式的角色及职责)
- Iterator : 迭代器接口,是系统提供,含义 hasNext, next, remove
- ConcreteIterator : 具体的迭代器类,管理迭代
- Aggregate :一个统一的聚合接口, 将客户端和具体聚合解耦
- ConcreteAggreage:具体的聚合,持有对象集合,并提供方法返回一个可以遍历此集合类型的迭代器,该迭代器可以正确遍历集合
- Client:客户端,通过Iterator 和Aggregate 依赖子类
迭代器模式应用实例
UML类图
代码示例
//具体迭代器实现
//数组类型集合的迭代器
public class ComputerCollegeIterator implements Iterator {
/**这里我们需要知道Department 是以怎样的方式存放的 => 数组*/
Department[] departments;
/**遍历的位置*/
int position = 0;
public ComputerCollegeIterator(Department[] departments) {
this.departments = departments;
}
@Override
public boolean hasNext() {
//判断是否还有下一个
if (position >= departments.length || departments[position] == null){
return false;
}else {
return true;
}
}
@Override
public Object next() {
return departments[position++];
}
/**空实现*/
@Override
public void remove() {}
}
//List类型集合的迭代器
public class InfoCollegeIterator implements Iterator {
/**信息工程学院是以List的方式存放 系的*/
List<Department> departmentList;
/**遍历的位置*/
int index = -1;
public InfoCollegeIterator(List<Department> departmentList) {
this.departmentList = departmentList;
}
@Override
public boolean hasNext() {
if (index >= departmentList.size() - 1){
return false;
}else {
++index;
return true;
}
}
@Override
public Object next() {
return departmentList.get(index);
}
/**空实现*/
@Override
public 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() {
this.departments = new Department[5];
addDepartment("java专业","java专业");
addDepartment("C专业","c专业");
addDepartment("C++专业","c++专业");
}
@Override
public String getName() {
return "计算机学院";
}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departments[numOfDepartment++] = department;
}
//返回一个可以遍历departments集合类型的迭代器
@Override
public Iterator createIterator() {
return new ComputerCollegeIterator(departments);
}
}
//具体实现 对象集合的类型是List的
public class InfoCollege implements College {
List<Department> departmentList;
public InfoCollege() {
this.departmentList = new ArrayList<>();
addDepartment("信息安全专业","信息安全专业");
addDepartment("网络安全专业","网络安全专业");
addDepartment("服务器专业","服务器专业");
}
@Override
public String getName() {
return "信息工程学院";
}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departmentList.add(department);
}
@Override
public Iterator createIterator() {
return new InfoCollegeIterator(departmentList);
}
}
//-----------------------------------------------------------------------------
//封装一层用于输出 完成对所有学院的输出
public class OutPutImpl {
/**学院集合*/
List<College> collegeList;
public OutPutImpl(List<College> collegeList) {
this.collegeList = collegeList;
}
/**遍历所有学院,然后调用printDepartment 输出各个学院的系*/
public void printCollege(){
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 next = (Department)iterator.next();
System.out.println(next.getName());
}
}
}
//-----------------------------------------------------------------------------
//客户端
public class Client {
public static void main(String[] args) {
//创建学院集合
List<College> collegeList = new ArrayList<>();
//创建学院
ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();
//将学院存入集合
collegeList.add(computerCollege);
collegeList.add(infoCollege);
//创建输出类 传入学院集合
OutPutImpl outPut = new OutPutImpl(collegeList);
//调用方法
outPut.printCollege();
}
}
迭代器模式在JDK-ArrayList集合应用的源码分析
过程
UML类图
角色分析说明
- 内部类Itr 充当具体实现迭代器Iterator 的类, 作为ArrayList 内部类
- List 就是充当了聚合接口,含有一个iterator() 方法,返回一个迭代器对象
- ArrayList 是实现聚合接口List 的子类,实现了iterator()
- Iterator 接口系统提供
- 迭代器模式解决了 不同集合(ArrayList ,LinkedList) 统一遍历问题
源码部分
迭代器模式的注意事项和细节
优点
- 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
- 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
- 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变 的话,只影响到了迭代器。
- 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
缺点
- 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
观察者模式
应用案例 - 天气预报项目需求
- 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如 发布到自己的网站或第三方)。
- 需要设计开放型API,便于其他第三方也能接入气象站获取数据。
- 提供温度、气压和湿度的接口
- 测量数据更新时,要能实时的通知给第三方
天气预报设计方案1 - 普通方案
示意图
代码示例
//气象站角色
/**
* 类的核心
* 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 float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
currentConditions.update(getTemperature(), getPressure(), getHumidity());
}
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();
}
}
//-----------------------------------------------------------------------------
//具体需要天气信息的类(站点) 观察者角色
public class CurrentConditions{
/**温度,气压,湿度*/
private float temperature;
private float pressure;
private float humidity;
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+"***");
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class Client {
public static void main(String[] args) {
CurrentConditions currentConditions = new CurrentConditions();
WeatherData weatherData = new WeatherData(currentConditions);
weatherData.setData(12,130,30);
}
}
问题分析
- 其他第三方接入气象站获取数据的问题
- 无法在运行时动态的添加第三方 (新浪网站)
- 违反ocp原则=>观察者模式
- 如果要扩展需要在WeatherData类中新增成员变量,构造器,以及修改dataChange()方法,不利于维护
观察者模式(Observer)原理
观察者聚合/管理类
观察者
说明
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化,比如这里的奶站是 Subject,是1的一方。用户是Observer,是多的一方。
观察者模式解决天气预报需求
UML类图
代码示例
//气象站接口/抽象层 让WeatherData实现 观察者聚合/管理类的抽象层
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
/**通知所有Observer*/
void notifyObservers();
}
//==================================
//具体观察者管理类实现
/**
* 类的核心
* 1.包含最新的天气情况信息
* 2.含有观察者集合使用ArrayList管理
* 3.当数据有更新时 就主动的调用 ArrayList中的所有对象的update方法(含display)
* 通知所有的接入方,最新消息
*/
public class WeatherData implements Subject{
private float temperatrue;
private float pressure;
private float humidity;
/**观察者集合*/
private List<Observer> observers;
public WeatherData() {
observers = new ArrayList<>();
}
public float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
notifyObservers();
}
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();
}
/**添加一个观察者*/
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
/**删除一个观察者*/
@Override
public void removeObserver(Observer o) {
if (observers.contains(o)){
observers.remove(o);
}
}
/**遍历所有的观察者,并通知*/
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this.temperatrue,this.pressure,this.humidity);
}
}
}
//-----------------------------------------------------------------------------
//观察者接口/抽象层
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;
@Override
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("***CurrentConditions mTemperature: "+temperature+"***");
System.out.println("***CurrentConditions mPressure: "+pressure+"***");
System.out.println("***CurrentConditions mHumidity: "+humidity+"***");
}
}
//扩展
public class Baidu implements Observer {
/**温度,气压,湿度*/
private float temperature;
private float pressure;
private float humidity;
@Override
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("扩展--------百度--------");
System.out.println("***百度 mTemperature: "+temperature+"***");
System.out.println("***百度 mPressure: "+pressure+"***");
System.out.println("***百度 mHumidity: "+humidity+"***");
}
}
//-----------------------------------------------------------------------------
//客户端
public class Client {
public static void main(String[] args) {
//创建一个WeatherData
WeatherData weatherData = new WeatherData();
//创建观察者
CurrentConditions currentConditions = new CurrentConditions();
Baidu baidu = new Baidu();
//注册到weatherData中
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baidu);
//取消观察
weatherData.removeObserver(currentConditions);
System.out.println("通知各个注册的观察者");
weatherData.setData(23,120,5);
}
}
观察者模式的好处
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则。
观察者模式在Jdk - Observable类应用的源码分析
源码分析
UML类图
说明
- Observable:作用和地位等价于前面案例的Subject接口,Observable是类不是接口,类中已经实现了核心的方法 ,即管理Observer的方法add.. delete…notify…
- Observer:作用和地位等价于我们前面讲过的Observer, 有update
- Observable和Observer的使用方法和前面讲过的一样,只是Observable是类,通过继承来实现观察者模式
中介者模式
应用案例 - 智能家庭项目
- 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等
- 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流 程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
传统方案解决智能家庭管理问题
类图
传统的方式的问题分析
- 当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
- 各个电器对象彼此联系,你中有我,我中有你,不利于松耦合.
- 各个电器对象之间所传递的消息(参数),容易混乱
- 当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想,考虑中介者模式
中介者模式基本介绍
- 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。 中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
- 中介者模式属于行为型模式,使代码易于维护
- 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中 介者,在前后端交互时起到了中间人的作用
中介者模式的原理类图
原理类图
对原理类图的说明-即(中介者模式的角色及职责)
- Mediator:就是抽象中介者,定义了同事对象到中介者对象的接口
- Colleague:是抽象同事类
- ConcreteMediator:具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理HashMap,并接收某个同事对象的消息,完成相应的任务
- ConcreteColleague:具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其它同事类的行为(方法),但是他们都依赖中介对象
中介者模式应用实例 - 智能家庭管理
UML类图
中介者模式 - 智能家庭操作流程
- 创建ConcreMediator对象
- 创建各个同事类对象,比如:Alam、CoffeeMachine、TV…
- 在创建同事类对象的时候,就直接通过构造器,加入到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>();
}
@Override
public void Register(String colleagueName, Colleague colleague) {
colleagueMap.put(colleagueName, colleague);
if (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. 中介者在这个方法,协调各个具体的同事对象,完成任务
@Override
public void GetMessage(int stateChange, String colleagueName) {
//处理闹钟发出的消息
if (colleagueMap.get(colleagueName) instanceof Alarm) {
if (stateChange == 0) {
((CoffeeMachine) (colleagueMap.get(interMap
.get("CoffeeMachine")))).StartCoffee();
((TV) (colleagueMap.get(interMap.get("TV")))).StartTv();
} else if (stateChange == 1) {
((TV) (colleagueMap.get(interMap.get("TV")))).StopTv();
}
} else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
((Curtains) (colleagueMap.get(interMap.get("Curtains"))))
.UpCurtains();
} else if (colleagueMap.get(colleagueName) instanceof TV) {
//如果TV发出消息
} else if (colleagueMap.get(colleagueName) instanceof Curtains) {
//如果是以窗帘发出的消息,这里处理...
}
}
@Override
public void SendMessage() {
}
}
//-----------------------------------------------------------------------------
//同事抽象类
public abstract class Colleague {
//中介对象
private Mediator mediator;
public String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public Mediator GetMediator() {
return this.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);
}
@Override
public void SendMessage(int stateChange) {
//调用的中介者对象的getMessage
this.GetMediator().GetMessage(stateChange, this.name);
}
}
//具体的同事类
public class CoffeeMachine extends Colleague {
public CoffeeMachine(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}
public void StartCoffee() {
System.out.println("It's time to startcoffee!");
}
public void FinishCoffee() {
System.out.println("After 5 minutes!");
System.out.println("Coffee is ok!");
SendMessage(0);
}
}
//具体的同事类
public class Curtains extends Colleague {
public Curtains(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}
public void UpCurtains() {
System.out.println("I am holding Up Curtains!");
}
}
//具体的同事类
public class TV extends Colleague {
public TV(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}
@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}
public void StartTv() {
System.out.println("It's time to StartTv!");
}
public void StopTv() {
System.out.println("StopTv!");
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class ClientTest {
public static void main(String[] args) {
//创建一个中介者对象
Mediator mediator = new ConcreteMediator();
//创建Alarm 并且加入到 ConcreteMediator 对象的HashMap
Alarm alarm = new Alarm(mediator, "alarm");
//创建了CoffeeMachine 对象,并 且加入到 ConcreteMediator 对象的HashMap
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator,
"coffeeMachine");
//创建 Curtains , 并 且加入到 ConcreteMediator 对象的HashMap
Curtains curtains = new Curtains(mediator, "curtains");
TV tV = new TV(mediator, "TV");
//让闹钟发出消息
alarm.SendAlarm(0);
coffeeMachine.FinishCoffee();
alarm.SendAlarm(1);
}
}
中介者模式的注意事项和细节
- 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
- 减少类间依赖,降低了耦合,符合迪米特原则
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
备忘录模式
应用案例 - 游戏角色状态恢复问题
游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
传统方案解决游戏角色恢复
关系图
传统的方式的问题分析
- 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不 利于管理,开销也很大.
- 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
- 解决方案: => 备忘录模式
备忘录模式基本介绍
- 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情, 或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录 模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某 些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
- 备忘录模式属于行为型模式
备忘录模式原理类图
UML类图
对原理类图的说明-即(备忘录模式的角色及职责)
- originator : 需要保存状态的对象
- Memento : 备忘录对象,负责保存好记录,即Originator内部状态
- Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效 率
- 说明:如果希望保存多个originator对象的不同时间的状态也可以,只需要要HashMap<String,集合>
代码示例
//需要被保存状态的类
public class Originator {
/**状态信息*/
private String state;
/**编写一个方法,可以保存一个状态对象Memento 返回一个Memento*/
public Memento saveStateMemento(){
return new Memento(state);
}
/**通过备忘录对象,恢复状态*/
public void getStateFromMemento(Memento memento){
state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
//-----------------------------------------------------------------------------
//备忘录对象 用于备份状态
public class Memento {
/**状态信息*/
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
//-----------------------------------------------------------------------------
//守护者对象 管理备忘录对象
public class Caretaker {
/**在List集合中会有很多的备忘录对象*/
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento){
mementoList.add(memento);
}
/**
* 获取到第index个Originator 的备忘录对象(即保存的状态)
* Originator 对象下标为index的备忘录对象(状态)
* @param index 索引
* @return
*/
public Memento get(int index){
return mementoList.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.saveStateMemento());
originator.setState("状态2 攻击力:70");
caretaker.add(originator.saveStateMemento());
originator.setState("状态3 攻击力:30");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是:" + originator.getState());
//希望得到状态1 并将originator恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1后的状态是:" + originator.getState());
}
}
游戏角色恢复状态实例
UML类图
代码示例
//需要被保存状态的类 游戏角色类
blic class GameRole {
/**攻击力*/
private int vit;
/**防御力*/
private int def;
/**创建Memento 即根据当前的状态得到Memento*/
public Memento createMemento(){
return new Memento(vit,def);
}
/**从备忘录对象,恢复GameRole的状态*/
public void recoverGameRoleFromMemento(Memento memento){
this.vit = memento.getVit();
this.def = memento.getDef();
}
/**显示当前游戏角色的状态*/
public void display(){
System.out.printf("当前游戏角色的 攻击力:%d 防御力:%d
",this.vit,this.def);
}
public GameRole(int vit, int def) {
this.vit = vit;
this.def = def;
}
//getter setter省略...
}
//-----------------------------------------------------------------------------
//备忘录对象
public class Memento {
/**攻击力*/
private int vit;
/**防御力*/
private int def;
public Memento(int vit, int def) {
this.vit = vit;
this.def = def;
}
//getter setter省略...
}
//-----------------------------------------------------------------------------
//守护者对象,保存游戏角色的状态 管理备忘录对象
public class Caretaker {
/**如果只保存一次状态*/
private Memento memento;
/**对GameRole 保存多次状态;*/
//private List<Memento> mementos;
/**对多个游戏角色保存多个状态*/
//private Map<String,List<Memento>> rolesMementos;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class Client {
public static void main(String[] args) {
//创建游戏角色
GameRole gameRole = new GameRole(100, 100);
System.out.println("和Boss大战前的状态:");
gameRole.display();
//把当前状态保存
Caretaker caretaker = new Caretaker();
caretaker.setMemento(gameRole.createMemento());
System.out.println("和Boss开始大战~~~~~~~~~~~~~");
gameRole.setVit(30);
gameRole.setDef(30);
gameRole.display();
System.out.println("大战后,使用备忘录对象恢复到大战前");
gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
System.out.println("恢复后的状态:");
gameRole.display();
}
}
备忘录模式的注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
- 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 5、数据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
解释器模式
应用案例 - 四则运算问题
- 通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求
- 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
- 在分别输入a ,b, c, d, e 的值
传统方案解决四则运算问题分析
- 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
- 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰.
- 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果
解释器模式基本介绍
- 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
- 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
- 应用场景
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表达
- 一个简单语法需要解释的场景
- 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
解释器模式的原理类图
UML类图
对原理类图的说明-即(解释器模式的角色及职责)
- Context: 是环境角色,含有解释器之外的全局信息.
- AbstractExpression: 抽象表达式, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的点所共享
- TerminalExpression: 为终结符表达式, 实现与文法中的终结符相关的解释操作
- NonTermialExpression: 为非终结符表达式,为文法中的非终结符实现解释操作
- 说明:输入Context和TerminalExpression信息通过Client 输入即可
解释器模式来实现四则运算
UML类图
代码示例
//解释器最高抽象类,抽象类表达式,通过HashMap 键值对, 可以获取到变量的值
public abstract class Expression {
// a + b - c
// 解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value就是就是具体值
// HashMap {a=10, b=20}
public abstract int interpreter(HashMap<String, Integer> var);
}
//=========================================
/**
* 变量的解释器 a,b,c... (终结符表达式)
*/
public class VarExpression extends Expression {
private String key; // key=a,key=b,key=c
public VarExpression(String key) {
this.key = key;
}
// var 就是{a=10, b=20}
// interpreter 根据 变量名称,返回对应值
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
//=========================================
/**
* 抽象运算符号解析器(+,-...) 这里,每个运算符号,都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
*/
public class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
//因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}
//加法解释器
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//处理相加
//var 仍然是 {a=10,b=20}..
//一会我们debug 源码,就ok
@Override
public int interpreter(HashMap<String, Integer> var) {
//super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10
//super.right.interpreter(var): 返回right 表达式对应值 b = 20
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
//减法解释器
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
//求出left 和 right 表达式相减后的结果
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
//-----------------------------------------------------------------------------
//计数器类
public class Calculator {
// 定义表达式
private Expression expression;
/**
* 构造函数传参,并解析
* 构造器结束后expression 会是最后的计算表达式 会出现递归的形式计算
* 例:a+b+c+d
* left:(left(left:a right:b) right:c)right:d
*/
public Calculator(String expStr) { // expStr = a+b
// 安排运算先后顺序
Stack<Expression> stack = new Stack<>();
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a, +, b]
Expression left = null;
Expression right = null;
//遍历我们的字符数组, 即遍历 [a, +, b]
//针对不同的情况,做处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+':
left = stack.pop();// 从stack取出left => "a"
right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
stack.push(new AddExpression(left, right));// 然后根据得到left 和 right 构建 AddExpresson加入stack
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
//如果是一个 Var 就创建要给 VarExpression 对象,并push到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
//当遍历完整个 charArray 数组后,stack 就得到最后Expression
this.expression = stack.pop();
}
/**
* 计算最后expression表达式的结果
*/
public int run(HashMap<String, Integer> var) {
//最后将表达式a+b和 var = {a=10,b=20}
//然后传递给expression的interpreter进行解释执行
return this.expression.interpreter(var);
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class ClientTest {
public static void main(String[] args) throws IOException {
String expStr = getExpStr(); // a+b
HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
// 获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
// 获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
解释器模式在Spring - SpelExpressionParser类应用的源码剖析
关系图
说明
-
Expression接口表达式接口
-
下面有不同的实现类,比如SpelExpression, 或者CompositeStringExpression。
-
使用时候,根据你创建的不同的Parser对象,返回不同的Expression对象
public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { if (context == null) { context = NON_TEMPLATE_PARSER_CONTEXT; } if (context.isTemplate()) { return parseTemplate(expressionString, context); //返回的就是 CompositeStringExpression } else { return doParseExpression(expressionString, context); //返回的就是 SpelExpression } }
-
使用得当 Expression对象,调用getValue解释执行 表达式,最后得到结果
解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低
状态模式
应用案例 - APP抽奖活动问题
请编写程序完成APP抽奖活动 具 体要求如下:
- 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
- 奖品数量固定,抽完就不能 抽奖
- 活动有四个状态: 可以抽奖、 不能抽奖、发放奖品和奖品 领完
- 活动的四个状态转换关系图
状态模式基本介绍
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
状态模式原理类图
对原理类图的说明-即(状态模式的角色及职责)
- Context 类为环境角色, 用于维护State实例,这个实例定义当前状态
- State 是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为
- ConcreteState 具体的状态角色,每个子类实现一个与Context的一个状态相关行为
状态模式解决APP抽奖问题
UML类图
代码示例
//状态抽象类
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
//======================================
//不能抽奖状态
public class NoRaffleState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
// 当前状态可以扣积分 , 扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
// 当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}
// 当前状态不能发奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
//可以抽奖的状态
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
//已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}
//可以抽奖, 抽完奖后,根据实际情况,改成新的状态
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
// 10%中奖机会
if(num == 0){
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
}else{
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
// 不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
//发放奖品的状态
public class DispenseState extends State {
// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
if(activity.getCount() > 0){
System.out.println("恭喜中奖了");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
}else{
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕, 后面我们就不可以抽奖
activity.setState(activity.getDispensOutState());
//System.out.println("抽奖活动结束");
//System.exit(0);
}
}
}
//奖品发放完毕状态
//说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束
public class DispenseOutState extends State {
// 初始化时传入活动引用
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}
//-----------------------------------------------------------------------------
public class RaffleActivity {
// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}
}
//这里请大家注意,每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}
//其余的getter、setter省略...其它都是默认实现就行
}
//-----------------------------------------------------------------------------
//客户端,测试
public class ClientTest {
public static void main(String[] args) {
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
// 我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();
// 第二步抽奖
activity.raffle();
}
}
}
状态模式在实际项目 - 借贷平台 源码剖析
-
借贷平台的订单,有审核-发布-抢单 等等 步骤,随着操作的不同,会改变订单的状态, 项目中的这个模块实现就会使用到状态模式
-
通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下
设计图
UML类图
代码示例
//状态接口 public interface State { /** * 电审 */ void checkEvent(Context context); /** * 电审失败 */ void checkFailEvent(Context context); /** * 定价发布 */ void makePriceEvent(Context context); /** * 接单 */ void acceptOrderEvent(Context context); /** * 无人接单失效 */ void notPeopleAcceptEvent(Context context); /** * 付款 */ void payOrderEvent(Context context); /** * 接单有人支付失效 */ void orderFailureEvent(Context context); /** * 反馈 */ void feedBackEvent(Context context); String getCurrentState(); } //----------------------------------------------------------------------------- //状态实体类的抽象父类 public abstract class AbstractState implements State { protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允许"); //抽象类,默认实现了 State 接口的所有方法 //该类的所有方法,其子类(具体的状态类),可以有选择的进行重写 @Override public void checkEvent(Context context) { throw EXCEPTION; } @Override public void checkFailEvent(Context context) { throw EXCEPTION; } @Override public void makePriceEvent(Context context) { throw EXCEPTION; } @Override public void acceptOrderEvent(Context context) { throw EXCEPTION; } @Override public void notPeopleAcceptEvent(Context context) { throw EXCEPTION; } @Override public void payOrderEvent(Context context) { throw EXCEPTION; } @Override public void orderFailureEvent(Context context) { throw EXCEPTION; } @Override public void feedBackEvent(Context context) { throw EXCEPTION; } } //----------------------------------------------------------------------------- //具体状态实现子类 各种具体状态类 class FeedBackState extends AbstractState { @Override public String getCurrentState() { return StateEnum.FEED_BACKED.getValue(); } } class GenerateState extends AbstractState { @Override public void checkEvent(Context context) { context.setState(new ReviewState()); } @Override public void checkFailEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.GENERATE.getValue(); } } class NotPayState extends AbstractState { @Override public void payOrderEvent(Context context) { context.setState(new PaidState()); } @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.NOT_PAY.getValue(); } } class PaidState extends AbstractState { @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PAID.getValue(); } } class PublishState extends AbstractState { @Override public void acceptOrderEvent(Context context) { //把当前状态设置为 NotPayState。。。 //至于应该变成哪个状态,有流程图来决定 context.setState(new NotPayState()); } @Override public void notPeopleAcceptEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PUBLISHED.getValue(); } } class ReviewState extends AbstractState { @Override public void makePriceEvent(Context context) { context.setState(new PublishState()); } @Override public String getCurrentState() { return StateEnum.REVIEWED.getValue(); } } //----------------------------------------------------------------------------- //状态枚举类 public enum StateEnum { //订单生成 GENERATE(1, "GENERATE"), //已审核 REVIEWED(2, "REVIEWED"), //已发布 PUBLISHED(3, "PUBLISHED"), //待付款 NOT_PAY(4, "NOT_PAY"), //已付款 PAID(5, "PAID"), //已完结 FEED_BACKED(6, "FEED_BACKED"); private int key; private String value; StateEnum(int key, String value) { this.key = key; this.value = value; } public int getKey() {return key;} public String getValue() {return value;} } //----------------------------------------------------------------------------- //环境上下文 public class Context extends AbstractState{ //当前的状态 state, 根据我们的业务流程处理,不停的变化 private State state; @Override public void checkEvent(Context context) { state.checkEvent(this); getCurrentState(); } @Override public void checkFailEvent(Context context) { state.checkFailEvent(this); getCurrentState(); } @Override public void makePriceEvent(Context context) { state.makePriceEvent(this); getCurrentState(); } @Override public void acceptOrderEvent(Context context) { state.acceptOrderEvent(this); getCurrentState(); } @Override public void notPeopleAcceptEvent(Context context) { state.notPeopleAcceptEvent(this); getCurrentState(); } @Override public void payOrderEvent(Context context) { state.payOrderEvent(this); getCurrentState(); } @Override public void orderFailureEvent(Context context) { state.orderFailureEvent(this); getCurrentState(); } @Override public void feedBackEvent(Context context) { state.feedBackEvent(this); getCurrentState(); } public State getState() { return state; } public void setState(State state) { this.state = state; } @Override public String getCurrentState() { System.out.println("当前状态 : " + state.getCurrentState()); return state.getCurrentState(); } } //----------------------------------------------------------------------------- /**测试类*/ public class ClientTest { public static void main(String[] args) { //创建context 对象 Context context = new Context(); //将当前状态设置为 PublishState context.setState(new PublishState()); context.getCurrentState(); //publish --> not pay context.acceptOrderEvent(context); //not pay --> paid context.payOrderEvent(context); /* // 失败, 检测失败时,会抛出异常 try { context.checkFailEvent(context); System.out.println("流程正常.."); } catch (Exception e) { System.out.println(e.getMessage()); }*/ } }
状态模式的注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一 个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
- 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
策略模式
应用案例 - 鸭子问题
编写鸭子项目,具体要求如下:
- 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)
- 显示鸭子的信息
传统方案解决鸭子问题
设计图
UML类图
代码示例
//鸭子抽象层
public abstract class Duck {
public 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 PeKingDuck extends Duck {
@Override
public void display() {
System.out.println("北京鸭~");
}
/**因为北京鸭不能飞翔 因此需要重写fly*/
@Override
public void fly() {
System.out.println("北京鸭不能飞翔~");
}
}
public class WildDuck extends Duck {
@Override
public void display() {
System.out.println("这是野鸭~");
}
}
publics class ToyDuck extends Duck {
@Override
public void display() {
System.out.println("玩具鸭~");
}
//需要重写父类的所有方法
@Override
public void quack(){
System.out.println("玩具鸭不能嘎嘎叫~~");
}
@Override
public void swim(){
System.out.println("玩具鸭不能游泳~~");
}
@Override
public void fly(){
System.out.println("玩具鸭不能飞行~~");
}
}
传统方式解决鸭子问题分析和解决方案
- 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
- 上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
- 为了改进1问题,我们可以通过覆盖fly方法来解决 => 覆盖解决
- 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck的所有实现的方法 => 解决思路 策略模式 (strategy pattern)
策略模式基本介绍
- 策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以 互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
策略模式的原理类图
从上图可以看到,客户context 有成员变量strategy或者其他的策略接口(方法),至于需要使用到哪个策略,我们可以在构造器中指定
策略模式解决鸭子问题
UML类图
代码示例
//飞翔策略抽象层
public interface FlyBehavior {
/**子类具体实现*/
void fly();
}
//========================================
//具体实现子类
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术高超~~~~~~");
}
}
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术一般~~~~~~~~");
}
}
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞翔~~~~~~~");
}
}
//-----------------------------------------------------------------------------
//鸭子抽象层
public abstract class Duck {
/**策略接口*/
protected FlyBehavior flyBehavior;
public Duck() {
}
/**显示鸭子信息*/
public abstract void display();
/**叫*/
public void quack(){
System.out.println("鸭子嘎嘎叫~~");
}
/**游泳*/
public void swim(){
System.out.println("鸭子会游泳~~");
}
/**飞行*/
public void fly(){
if (flyBehavior != null){
display();
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
//-----------------------------------------------------------------------------
//鸭子具体实现
//野鸭
public class WildDuck extends Duck {
/**构造器传入 FlyBehavior对象*/
public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println("这是野鸭~");
}
}
//北京鸭
public class PeKingDuck extends Duck {
public PeKingDuck() {
/**假如北京鸭可以飞翔但是飞翔技术一般 因此需要将flyBehavior设置为飞翔技术一般的实现子类*/
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("北京鸭~");
}
}
//玩具鸭
public class ToyDuck extends Duck {
public ToyDuck() {
//设置玩具鸭不能飞翔
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
System.out.println("玩具鸭~");
}
//需要重写父类的方法
@Override
public void quack(){
System.out.println("玩具鸭不能嘎嘎叫~~");
}
@Override
public void swim(){
System.out.println("玩具鸭不能游泳~~");
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();
PeKingDuck peKingDuck = new PeKingDuck();
peKingDuck.fly();
//动态改变某个对象的行为
//例:北京鸭不能飞
peKingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭新的行为:");
peKingDuck.fly();
}
}
策略模式在JDK-Arrays 应用的源码分析
JDK的 Arrays 的Comparator就使用了策略模式
代码示例
//本次方式匿名的方式实现了策略模式
public class Strategy {
public static void main(String[] args) {
//方式1
Integer[] data = {9, 1, 2, 8, 4, 3};
// 实现升序排序,返回-1放左边,1放右边,0保持不变
/*
说明:
1. 实现了 Comparator 接口(策略接口) , 匿名类对象 new Comparator<Integer>(){..}
2. 对象 new Comparator<Integer>(){..} 就是实现了策略接口的具体策略对象
3. public int compare(Integer o1, Integer o2){} 指定具体的处理方式
*/
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return 1;
} else {
return -1;
}
}
};
/*说明sort方法:
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a); //默认方法
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c); //使用策略对象c
else
// 使用策略对象c
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
*/
//sort(要排序的数组,排序策略);
Arrays.sort(data, comparator);
System.out.println(Arrays.toString(data));
//方式2 使用lambda表达式实现策略模式 升序排序从小到大
Integer[] data2 = {19, 11, 12, 18, 14, 13};
Arrays.sort(data2,(var1,var2)->{
if(var1.compareTo(var2) > 0){
return 1;
}else {
return -1;
}
});
System.out.println(Arrays.toString(data2));
}
}
策略模式的注意事项和细节
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
职责链模式
应用案例 - OA系统采购审批需求
学校OA系统的采购审批项目:需求是
- 采购员采购教学器材
- 如果金额 小于等于5000, 由教学主任审批 (0<=x<=5000)
- 如果金额 小于等于10000, 由院长审批 (5000
- 如果金额 小于等于30000, 由副校长审批 (10000
- 如果金额 超过30000以上,有校长审批 ( 30000
传统方案解决OA系统审批
传统的设计方案(类图)
传统方案解决OA系统审批问题分析
- 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批人)完成审批。
- 传统方式的问题分析 : 客户端这里会使用到分支判断(比如 switch) 来对不同的采 购请求处理,这样就存在如下问题 (1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化 (2) 客户端必须明确的知道 有多少个审批级别和访问
- 这样 对一个采购请求进行处理 和 Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护
- 解决方案 =》 职责链模式
职责链模式基本介绍
- 职责链模式(Chain of Responsibility Pattern), 又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
- 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求, 那么它会把相同的请求传给下一个接收者,依此类推。
- 这种类型的设计模式属于行为型模式
职责链模式的原理类图
职责链模式(Chain Of Responsibility), 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关 系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.
对原理类图的说明-即(职责链模式的角色及职责)
- Handler:抽象的处理者, 定义了一个处理请求的接口, 同时含有另外Handler
- ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求,可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交给后继者去处理,从而形成一个职责链
- Request:含有很多属性,表示一个请求
职责链模式解决OA系统采购审批
UML类图
代码示例
//处理者抽象层
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);
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getPrice() <= 5000){
System.out.println("请求编号 id = " + request.getId() + " 被 " + this.name + " 处理");
}else {
//让后继者处理
this.approver.processRequest(request);
}
}
}
//院长
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getPrice() > 5000 && request.getPrice() <= 10000){
System.out.println("请求编号 id = " + request.getId() + " 被 " + this.name + " 处理");
}else {
//让后继者处理
this.approver.processRequest(request);
}
}
}
//副校长
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getPrice() > 10000 && request.getPrice() <= 30000){
System.out.println("请求编号 id = " + request.getId() + " 被 " + this.name + " 处理");
}else {
//让后继者处理
this.approver.processRequest(request);
}
}
}
//校长
public class SchoolMasterApprover extends Approver{
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest request) {
if (request.getPrice() > 30000){
System.out.println("请求编号 id = " + request.getId() + " 被 " + this.name + " 处理");
}else {
//让后继者处理
this.approver.processRequest(request);
}
}
}
//-----------------------------------------------------------------------------
//客户端,测试
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest request = new PurchaseRequest(1, 6000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("主任");
CollegeApprover collegeApprover = new CollegeApprover("院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("副校长");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("校长");
//需要将各个审批级别的下一个审批者设置好 (处理人构成一个环形链表)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(request);
}
}
职责链模式在SpringMVC框架应用的源
SpringMVC-HandlerExecutionChain 类就使用到职责链模式
SpringMVC请求流程简图
代码分析
public class ResponsibilityChain {
public static void main(String[] args) {
// DispatcherServlet 类中
//说明
/*
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
mappedHandler = getHandler(processedRequest);//获取到HandlerExecutionChain对象
//在 mappedHandler.applyPreHandle 内部得到了 HandlerInterceptor interceptor拦截器
//调用了拦截器的 interceptor.preHandle
if (!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];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
*/
}
}
源码截图
DispatcherServlet类中的doDispatch方法
applyPreHandle方法内部
applyPostHandle方法内部
说明
- springmvc 请求的流程图中,执行了 拦截器相关方法 interceptor.preHandler 等等
- 在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式
- HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
- HandlerExecutionChain 维护了 HandlerInterceptor 的集合, 可以向其中注册相应的拦截器.
职责链模式的注意事项和细节
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器