Bootstrap

设计模式篇(DesignPattern - 行为型模式)(持续更新迭代)(图片待加载)

目录

一、模版方法模式(制作豆浆问题)

1. 豆浆制作问题

2. 模板方法模式

2.1. 基本介绍

2.2. 代码实现

2.3. 钩子方法

2.4. 应用案例

应用一:Android中View的draw

应用二:Spring 框架应用的源码分析

2.5. 注意事项和细节

二、命令模式(智能生活问题)

1. 需求

2. 简介

3. 代码

4. 应用

Thread

Android 中的 Handler

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

三、访问者模式(歌手测评问题)

1. 需求

2. 传统方式

3. 访问者模式

3.1. 简介

3.2 代码

3.2. 双分派

3.3 注意事项和细节

四、迭代器模式(学校院系结构问题)

1. 需求

2. 简介

3. 代码

4. JDK-ArrayList 集合应用的源码分析

5. 注意事项和细节

五、观察者模式(天气预报问题)

1. 需求

2. 传统方案

2.1. 代码

2.2. 问题分析

3. 观察者和模式

3.1. 简介

3.2. 代码

3.3. 优缺点

3.4. JDK 应用的源码分析

六、中介者模式(智能家庭管理问题)

1. 需求

2. 传统方式

3. 中介者模式

3.1. 简介

3.2. 原理类图

3.3. 代码实现

3.4. 注意事项和细节

七、备忘录模式(游戏角色状态恢复问题)

1. 游戏角色状态恢复问题

2. 传统方式

3. 备忘录模式

3.1. 简介

3.2. 原理

3.3. 代码

3.4. 注意事项和细节

八、解释器模式(四则运算问题)

8.1 需求

8.2 传统方案解决四则运算问题分析

8.3 基本介绍

8.4 原理

8.2 代码实现

8.3 解释器模式的注意事项和细节

九、状态模式(APP 抽奖活动问题)

1. APP 抽奖活动问题

2.

状态模式

2.1. 简介

2.2. 原理类图

2.3. 代码实现

2.4. 实际项目-借贷平台 源码剖析

简介

代码

2.5. 注意事项和细节

十、策略模式(鸭子问题)

1. 简介

2. 策略模式

2.1. 简介

2.2. 代码

2.3. 在 JDK-Arrays 应用的源码分析

2.4. 注意事项和细节

十一、职责链模式(OA系统采购审批需求问题)

1. 需求

2. 职责链模式

2.1. 简介

2.2. 代码

2.3. 应用

2.4. 注意事项和细节


一、模版方法模式(制作豆浆问题)

1. 豆浆制作问题

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

  1. 制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  4. 请使用 模板方法模式完成

2. 模板方法模式

2.1. 基本介绍

  1. 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
  2. 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
  3. 这种类型的设计模式属于行为型模式。

对原理类图的说明-即(模板方法模式的角色及职责)

  1. AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operationr2,3,4
  2. ConcreteClass 实现抽象方法 operationr2,3,4, 以完成算法中特点子类的步骤

2.2. 代码实现

  1. 应用实例要求 编写制作豆浆的程序,说明如下: 制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎通过添加不同的配料,可以制作出不同口味的豆浆 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。。。)
  2. 思路分析和图解(类图)

抽象类,豆浆

public abstract class SoyaMilk {
    //模板方法 make
    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 RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("第二步,加入上等红豆");
    }
}

public class PeanutSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("第二步,加入上好的花生");
    }
}

客户端调用

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

2.3. 钩子方法

  1. 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
  2. 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

改造抽象类

public abstract class SoyaMilk {
    //模板方法 make
    final void make(){
        select();
        if (customerWantCondiments()) {
            addCondiments();
        }
        soak();
        beat();
    }
    
    ...
    
    //钩子方法,决定是否需要添加配料
    boolean customerWantCondiments() {
        return true;
    }
}

纯豆浆

public class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {

    }

    //重写钩子方法
    @Override
    boolean customerWantCondiments() {
        return false;
    }
}

客户端调用

main(...){
    System.out.println("--------制作纯豆浆--------");
    SoyaMilk pureSoyaMilk = new PureSoyaMilk();
    pureSoyaMilk.make();
    
    //输出
    --------制作纯豆浆--------
    第一步,选择好的新鲜黄豆
    第三步,黄豆和配料开始浸泡,需要3个小时
    第四步,黄豆和配料放到豆浆机去打碎
}

2.4. 应用案例

应用一:Android中View的draw
  1. Android中View的draw方法就是使用了模板方法模式
public class View{
    //钩子方法,空实现
    protected void onDraw(Canvas canvas) {
    }
    //钩子方法,空实现
    protected void dispatchDraw(Canvas canvas) {
    }
    //绘制方法,定义绘制流程
    public void draw(Canvas canvas) {
       //其他代码略

        /*
         *  绘制流程如下:
         *
         *      1. 绘制view背景
         *      2. 如果有需要,就保存图层
         *      3. 绘制view内容
         *      4. 绘制子View
         *      5. 如果有必要,绘制渐变框和恢复图层
         *      6. 绘制装饰(滑动条等)
         */

        if (!dirtyOpaque) {
            drawBackground(canvas);//步骤1. 绘制view背景
        }

        // 如果可能的话跳过第2步和第5步(常见情况)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            if (!dirtyOpaque) onDraw(canvas);//步骤3. 绘制view内容

            dispatchDraw(canvas);//步骤4. 绘制子View

            // 覆盖一部分内容,绘制前景
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            onDrawForeground(canvas); //步骤6. 绘制装饰(滑动条等)

            return;
        }
}

说明:

  • View的draw()方法中定义了一整套的绘制流程,这个流程是固定的,所有的Android中的View都是按照这个流程来绘制的。其中drawBackground()这个方法在View类中是实现了具体过程的,而onDraw()方法和dispatchDraw()方法在View中都是空实现,即都是钩子方法。不同的子类通过重写这些空实现来实现自身不同的绘制效果。
  • 具体的View,像TextView这些单一的View,就会重写onDraw()方法,由于TextView没有子View,所以dispatchDraw()还是空实现;而ViewGroup类含有子View,需要遍历子View并绘制,因此需要重写onDraw()和dispatchDraw()。
  • 所以,我们自定义View时必须且只需重写onDraw();自定义ViewGroup时则需要重写onDraw()和dispatchDraw()。
应用二:Spring 框架应用的源码分析

2.5. 注意事项和细节

  1. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  4. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  5. 一般模板方法都加上 final 关键字, 防止子类重写模板方法.
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理

二、命令模式(智能生活问题)

1. 需求

  1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作。
  2. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个 App,分别控制,我们希望只要一个 app 就可以控制全部智能家电。
  3. 要实现一个 app 控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给 app 调用,这时 就可以考虑使用命令模式。
  4. 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
  5. 在我们的例子中,动作的请求者是手机 app,动作的执行者是每个厂商的一个家电产品

2. 简介

  1. 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个, 我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
  2. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  3. 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  4. 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。 Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象

对原理类图的说明-即(命名模式的角色及职责)

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

3. 代码

ICommand 抽象命令者(Command角色)

public interface ICommand {
    void execute();//执行
    void undo();//撤销
}

电灯开关命令(ConcreteCommand)

public class LightOnCommand implements ICommand {
    LightReceiver receiver;

    public LightOnCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.on();
    }

    @Override
    public void undo() {
        receiver.off();
    }
}
public class LightOffCommand implements ICommand {
    LightReceiver receiver;

    public LightOffCommand(LightReceiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.off();
    }

    @Override
    public void undo() {
        receiver.on();
    }
}
public class NoCommand implements ICommand {
    @Override
    public void execute() {
    }
    @Override
    public void undo() {

    }
}

电灯对象 (接收者 Receiver角色)

public class LightReceiver {

    public void on(){
        System.out.println("开灯请睁眼");
    }
    public void off(){
        System.out.println("关灯请闭眼");
    }
}

遥控器(调用者角色 Invoker角色)

public class RemoteController {
    //开关按钮
    ICommand[] onCommands;
    ICommand[] offCommands;
    //撤销按钮
    ICommand undoCommand;

    public RemoteController() {
        onCommands  = new ICommand[5];
        offCommands  = new ICommand[5];
        for (int i = 0; i < 5; i++) {
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }

    }

    // 给我们的按钮设置你需要的命令
    public void setCommand(int no, ICommand onCommand, ICommand offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }

    public void onButtonClicked(int no){
        onCommands[no].execute();
        undoCommand = onCommands[no];
    }

    public void offButtonClicked(int no){
        offCommands[no].execute();
        undoCommand = offCommands[no];
    }

    public void undoButtonClicked(){
        undoCommand.undo();
    }
    
}

电视机对象

# 编写第2种家电
public class TVReceiver {
    public void on() {
        System.out.println(" 电视机打开了.. ");
    }
    
    public void off() {
        System.out.println(" 电视机关闭了.. ");
    }
}

电视机开关命令

public class TVOffCommand implements Command {

    // 聚合TVReceiver
    TVReceiver tv;

    // 构造器
    public TVOffCommand(TVReceiver tv) {
        super();
        this.tv = tv;
    }

    @Override
    public void execute() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        tv.off();
    }

    @Override
    public void undo() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        tv.on();
    }
}
public class TVOnCommand implements Command {

    // 聚合TVReceiver
    TVReceiver tv;

    // 构造器
    public TVOnCommand(TVReceiver tv) {
        super();
        this.tv = tv;
    }

    @Override
    public void execute() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        tv.on();
    }

    @Override
    public void undo() {
        // TODO Auto-generated method stub
        // 调用接收者的方法
        tv.off();
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        //创建电灯的对象 (接收者)
        LightReceiver receiver = new LightReceiver();
        //创建电灯的开关命令
        ICommand onCommand = new LightOnCommand(receiver);
        ICommand offCommand = new LightOffCommand(receiver);
        //创建遥控器
        RemoteController controller = new RemoteController();
        //给遥控器设置相关命令
        controller.setCommand(0,onCommand,offCommand);
        System.out.println("----按下开灯按钮----");
        controller.onButtonClicked(0);
        System.out.println("----按下关灯按钮----");
        controller.offButtonClicked(0);
        System.out.println("----按下撤销按钮----");
        controller.undoButtonClicked();

        System.out.println("----------开始操作电视机----------");
        TVReceiver tvReceiver = new TVReceiver();
        ICommand tvOnCommand = new TVOnCommand(tvReceiver);
        ICommand tvOffCommand = new TVOffCommand(tvReceiver);
        controller.setCommand(1,tvOnCommand,tvOffCommand);
        controller.onButtonClicked(1);
        controller.offButtonClicked(1);
        controller.undoButtonClicked();

    }
}

4. 应用

Thread

实际上Thread的使用就是一个简单的命令模式,先看下Thread的使用:

 new Thread(new Runnable() {
            @Override
            public void run() {
                //doSomeThing
            }
        }).start();

Thread的start()方法即命令的调用者,同时Thread的内部会调用Runnable的run(),这里Thread又充当了具体的命令角色,最后的Runnable则是接受者了,负责最后的功能处理。

Android 中的 Handler

另一个比较典型的常用到命令模式就是Handler了,这里就不贴代码了,简单分析下各个类的角色:

  • 接收者:Handler,执行消息的处理操作。
  • 调用者:Looper,调用消息的的处理方法。
  • 命令角色:Message,消息类。

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

  1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
  2. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  3. 容易实现对请求的撤销和重做
  4. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  5. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  6. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制

三、访问者模式(歌手测评问题)

1. 需求

完成测评系统需求

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

2. 传统方式

  1. 如果系统比较小,还是 ok 的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了 ocp 原则, 不利于维护
  2. 扩展性不好,比如 增加了 新的人员类型,或者管理方法,都不好做
  3. 引出我们会使用新的设计模式 – 访问者模式

3. 访问者模式

3.1. 简介

  1. 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
  2. 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
  3. 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  4. 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决

对原理类图的说明-即(访问者模式的角色及职责)

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

3.2 代码

  1. 将人分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等),请使用访问者模式来说实现
  2. 思路分析和图解(类图)

① 抽象访问者(Visitor角色)

public abstract class Action {
    //得到男性测评结果
    abstract void getManResult(Man man);
    //得到女性评测结果
    abstract void getWomanResult(Woman man);
}

② 具体访问者 (ConcreteVisitor角色)

public class Success extends Action{
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName()+",这个男人给的评价是很成功");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName()+",这个女人给的评价是很成功");
    }
}

public class Fail extends Action{
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName()+",这个男人给的评价是失败了");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName()+",这个女人给的评价是失败了");
    }
}

③ 抽象接收者(Element角色)

public abstract class Person {
    //提供一个方法 让访问者可以访问
    public abstract void accept(Action action);
}

④ 具体接收者

/**
 * 说明:
 * 1. 这里使用到了双分派,即首先在客户端程序中,将具体的状态作为参数传递到Woman或者Man中(第一次分派)
 * 2. 然后Woman类调用了作为参数的"具体方法"中方法getWomanResult,同时将自己(this)作为参数传入,完成了第二次分派
 */
public class Woman extends Person {
    @Override
    public void accept(Action action) {
        action.getWomanResult(this);
    }
}

public class Man extends Person {

    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}

⑤ 数据结构 (ObjectStructure 角色)

/**
 * @description 数据结构,管理很多人(Man  Woman)
 */
public class ObjectStructure {
    //维护了一个集合
    private List<Person> persons = new LinkedList<>();

    //增加到list
    public void attach(Person person){
        persons.add(person);
    }

    //删除
    public void detach(Person person){
        persons.remove(person);
    }

    //显示测评结果
    public void display(Action action){
        for (Person p: persons) {
            p.accept(action);
        }
    }
}

⑥ 客户端调用

public class Client {
    public static void main(String[] args) {
        //创建objectStructure
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new Man("张三"));
        objectStructure.attach(new Man("李四"));
        objectStructure.attach(new Woman("王小花"));
        //成功
        Action success = new Success();
        objectStructure.display(success);

        //失败
        Action fail = new Fail();
        objectStructure.display(fail);

    }
}

输出

张三,这个男人给的评价是很成功
李四,这个男人给的评价是很成功
王小花,这个女人给的评价是很成功
张三,这个男人给的评价是失败了
李四,这个男人给的评价是失败了
王小花,这个女人给的评价是失败了
张三,这个男人给的评价是待定
李四,这个男人给的评价是待定
王小花,这个女人给的评价是待定

3.2. 双分派

  • 上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
  • 以上述实例为例,假设我们要添加一个 Wait 的状态类,考察 Man 类和 Woman 类的反应,由于使用了双分派,只需增加一个 Action 子类即可在客户端调用即可,不需要改动任何其他类的代码

如: 新增待定评价

public class Wait extends Action {
    @Override
    void getManResult(Man man) {
        System.out.println(man.getName()+",这个男人给的评价是待定");
    }

    @Override
    void getWomanResult(Woman woman) {
        System.out.println(woman.getName()+",这个女人给的评价是待定");
    }
}

调用

main(...){
    //待定
    Action wait = new Wait();
    objectStructure.display(wait);
}

3.3 注意事项和细节

优点

  1. 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
  2. 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

缺点

  1. 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
  2. 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
  3. 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.

四、迭代器模式(学校院系结构问题)

1. 需求

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

一个学院有多个系。

传统的方式的问题分析

  1. 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  2. 实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的遍历的操作
  3. 解决方案:=> 迭代器模式

2. 简介

  1. 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
  2. 如果我们的集合元素是用不同的方式实现的,有数组,还有 java 的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  3. 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。

对原理类图的说明-即(迭代器模式的角色及职责)

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

3. 代码

  1. 应用实例要求 编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院, 一个学院有多个系。
  2. 设计思路分析

  1. 代码实现① 迭代器接口Iterator 使用jdk提供的java.util.Iterator② 具体的迭代器类 (ConcreteIterator 角色)
public class InfoCollegeIterator implements Iterator {

    List<Department> departmentList;//信息工程学院是以List方式存放系
    int index = -1;//遍历的位置

    public InfoCollegeIterator(List<Department> departmentList) {
        this.departmentList = departmentList;
    }

    @Override
    public boolean hasNext() {
        if (index>=departmentList.size()-1){
            return false;
        }
        index += 1;
        return true;
    }

    @Override
    public Object next() {
        Department department = departmentList.get(index);
        return department;
    }

    //删除方法默认空实现
    @Override
    public void remove() {

    }
}
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;
        }
        return true;
    }

    @Override
    public Object next() {
        Department department = departments[position];
        position += 1;
        return department;
    }

    //删除方法默认空实现
    @Override
    public void remove() {

    }
}

③ 统一的聚合接口 (Aggregate 角色)

public interface College {
    public String getName();
    //增加系的方法
    public void addDepartment(String name,String desc);
    //返回一个迭代器 遍历
    public Iterator createIterator();
}

④ 具体的聚合持有对象集合 (ConcreteAggreage 角色)

public class ComputerColleage implements College {

    Department[] departments;
    int numOfDepartment = 0;//保存当前数组的对象个数

    public ComputerColleage() {
        departments = new Department[5];
        addDepartment("Java","java专业");
        addDepartment("PHP","PHP专业");
        addDepartment("大数据","大数据专业");
    }

    @Override
    public String getName() {
        return "计算机学院";
    }

    @Override
    public void addDepartment(String name, String desc) {
        Department department = new Department(name,desc);
        departments[numOfDepartment] = department;
        numOfDepartment += 1;
    }

    @Override
    public Iterator createIterator() {
        return new ComputerCollegeIterator(departments);
    }
}
public class InfoCollegeIterator implements Iterator {

    List<Department> departmentList;//信息工程学院是以List方式存放系
    int index = -1;//遍历的位置

    public InfoCollegeIterator(List<Department> departmentList) {
        this.departmentList = departmentList;
    }

    @Override
    public boolean hasNext() {
        if (index>=departmentList.size()-1){
            return false;
        }
        index += 1;
        return true;
    }

    @Override
    public Object next() {
        Department department = departmentList.get(index);
        return department;
    }

    //删除方法默认空实现
    @Override
    public void remove() {

    }
}

⑤ 输出类(个人认为有点像外观模式中的外观类)

public class OutputImpl {
    //学院集合
    List<College> collegeList;
    public OutputImpl(List<College> collegeList) {
        this.collegeList = collegeList;
    }

    //遍历所有的许愿  然后调用printDepartment 输出各个学院的系
    public void printCollege(){
        //从collegeList去除所有学院
        //java 中的List已经实现了iterator
        Iterator<College> iterator = collegeList.iterator();
        while (iterator.hasNext()){
            College c = (College)iterator.next();
            System.out.println(c.getName());
            printDepartment(c.createIterator());
        }
    }
    
    //输出 学院输出系
    public void printDepartment(Iterator iterator){
        while (iterator.hasNext()){
            Department d = (Department)iterator.next();
            System.out.println(d.getName()+" -- "+d.getDesc());
        }
    }
}

⑥ 客户端调用

public class Client {
    public static void main(String[] args) {
        //创建学院
        ArrayList<College> collegeList = new ArrayList<>();
        ComputerColleage computerColleage = new ComputerColleage();
        InfoColleage infoColleage = new InfoColleage();
        collegeList.add(computerColleage);
        collegeList.add(infoColleage);
        OutputImpl output = new OutputImpl(collegeList);
        output.printCollege();
    }
}

4. JDK-ArrayList 集合应用的源码分析

JDK 的 ArrayList集合中就使用了迭代器模式

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> a = new ArrayList<>();
        a.add("jack");
        a.add("tom");
        Iterator<String> iterator = a.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

对类图的角色分析和说明

  • 内部类 Itr 充当具体实现迭代器 Iterator 的类, 作为 ArrayList 内部类
  • List 就是充当了聚合接口,含有一个 iterator() 方法,返回一个迭代器对象
  • ArrayList 是实现聚合接口 List 的子类,实现了 iterator()
  • Iterator 接口系统提供
  • 迭代器模式解决了 不同集合(ArrayList ,LinkedList) 统一遍历问题

5. 注意事项和细节

优点

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

缺点

  1. 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

五、观察者模式(天气预报问题)

1. 需求

天气预报项目需求,具体要求如下:

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

2. 传统方案

或者

2.1. 代码

天气情况 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 + "***");
    }
}

天气核心类 WeatherData

/**
 * 类是核心
 * 1. 包含最新的天气情况信息
 * 2. 含有 CurrentConditions 对象
 * 3.  当数据有更新时,就主动的调用	CurrentConditions 对象 update 方法(含 display),  这样他们(接入方)就看到最新的信息
 *
 * @author Administrator
 */
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() {
    //调用 接入方的 update
        currentConditions.update(getTemperature(), getPressure(), getHumidity());
    }

    //当数据有更新时,就调用 setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //调用 dataChange, 将最新的信息 推送给 接入方 currentConditions
        dataChange();
    }
}

客户端测试

public class Client {
    public static void main(String[] args) {
        //创建接入方 currentConditions
        CurrentConditions currentConditions = new CurrentConditions();
        //创建 WeatherData 并将 接入方 currentConditions 传递到 WeatherData 中
        WeatherData weatherData = new WeatherData(currentConditions);

        //更新天气情况
        weatherData.setData(30, 150, 40);
        //天气情况变化
        System.out.println("============天气情况变化=============");
        weatherData.setData(40, 160, 20);

    }
}

2.2. 问题分析

  1. 其他第三方接入气象站获取数据的问题
  2. 无法在运行时动态的添加第三方 (新浪网站)
  3. 违反 ocp 原则=>观察者模式//在 WeatherData 中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到 dataChange, 不利于维护,也不是动态加入

3. 观察者和模式

3.1. 简介

  1. 观察者模式类似订牛奶业务
  2. 奶站/气象局:Subject
  3. 用户/第三方网站:Observer
  • Subject:登记注册、移除和通知
    • registerObserver: 注 册
    • removeObserver: 移除
    • notifyObservers(): 通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送, 看具体需求定

Observer:接收输入

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

3.2. 代码

① Subject角色(主题) ,登记注册、移除和通知

抽象主题

public interface Subject {
    public void registerObserver(Observer observer);
    public void removeObserver(Observer observer);
    public void notifyObservers();
}

具体主题

/**
 * 类是核心
 * 1. 包含最新的天气情况信息
 * 2. 含有 观察者 使用ArrayList管理
 * 3.  当数据有更新时,就主动的调用	ArrayList  这样他们(接入方)就看到最新的信息
 *
 * @author Administrator
 */
public class WeatherData implements Subject{
    private float temperatrue;
    private float pressure;
    private float humidity;
    //观察者集合
    private ArrayList<Observer> observers;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    public float getTemperature() {
        return temperatrue;
    }

    public float getPressure() {
        return pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    //当数据有更新时,就调用 setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }

    public void dataChange() {
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        if (observers.contains(observer)){
            observers.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer: observers) {
            observer.update(getTemperature(),getPressure(),getHumidity());
        }
    }


}

② Observer:接收输入

抽象观察者

/**
 * @description 观察者接口,由观察者来实现
 */
public interface Observer {
    public void update(float temperature,float pressure,float humidity);
}

具体观察者

public class CurrentConditions implements Observer {
    // 温度,气压,湿度
    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 + "***");
    }
}

具体观察者,扩展天气服务站点

public class BaiduSite implements Observer {
    // 温度,气压,湿度
    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("***百度数据 气温: " + temperature + "***");
        System.out.println("***百度数据 气压: " + pressure + "***");
        System.out.println("***百度数据 湿度: " + humidity + "***");
    }
}

③ 客户端调用

public class Client {
    public static void main(String[] args) {
        //创建一个WeatherData
        Subject subject = new WeatherData();
        //创建观察者
        Observer observer = new CurrentConditions();
        Observer observer2 = new BaiduSite();
        //注册到weatherData
        subject.registerObserver(observer);
        subject.registerObserver(observer2);
        //通知各个注册的观察者
        ((WeatherData)subject).setData(10f,20f,30f);
    }
}

3.3. 优缺点

优点

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类 WeatherData 不会修改代码, 遵守了 ocp 原则。
  3. 解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
  4. 易于扩展,对同一主题新增观察者时无需修改原有代码。

缺点

  • 依赖关系并未完全解除,抽象主题仍然依赖抽象观察者。
  • 使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
  • 可能会引起多余的数据通知。

3.4. JDK 应用的源码分析

Jdk 的 Observable 类就使用了观察者模式

public interface Observer {//(抽象观察者
    //只定义了一个update方法
    void update(Observable o, Object arg);
}

public class Observable {//抽象被观察者
    private boolean changed = false;//定义改变状态,默认为false
    private final ArrayList<Observer> observers;//定义一个观察者list

    public Observable() {//构造函数,初始化一个观察者list来保存观察者
        observers = new ArrayList<>();
    }
    //添加观察者,带同步字段的,所以是线程安全的
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    //删除观察者
    public synchronized void deleteObserver(Observer o) {
        observers.remove(o);
    }

    //通知所以观察者,无参数
    public void notifyObservers() {
        notifyObservers(null);
    }

     //通知所有观察者,带参数
    public void notifyObservers(Object arg) {

        Observer[] arrLocal;
        //加synchronized字段,保证多线程下操作没有问题
        synchronized (this) {
            if (!hasChanged())//这里做了是否发生改变的判断,是为了防止出现无意义的更新
                return;

            arrLocal = observers.toArray(new Observer[observers.size()]);//ArrayList转换成一个临时的数组,这样就防止了通知,添加,移除同时发生可能导致的异常
            clearChanged();///清除改变状态,设置为false
        }
        //遍历逐一通知
        for (int i = arrLocal.length-1; i>=0; i--)
            arrLocal[i].update(this, arg);
    }

    //清楚所有观察者
    public synchronized void deleteObservers() {
        observers.clear();
    }

    //设置被观察者为改变状态,设置为true
    protected synchronized void setChanged() {
        changed = true;
    }

    //清除改变状态,设置为false
    protected synchronized void clearChanged() {
        changed = false;
    }

    //返回当前的改变状态
    public synchronized boolean hasChanged() {
        return changed;
    }

    //观察者数量
    public synchronized int countObservers() {
        return observers.size();
    }
}

模式角色分析

  • Observable 的作用和地位等价于 我们前面讲过Subject
  • Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理 Observer 的方法 add.. delete .. notify...
  • Observer 的作用和地位等价于我们前面讲过的 Observer, 有 update
  • Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式

六、中介者模式(智能家庭管理问题)

1. 需求

需求:智能家庭项目

  1. 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等
  2. 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下-

电视机开始播放

2. 传统方式

传统的方式的问题分析

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

3. 中介者模式

3.1. 简介

  1. 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  2. 中介者模式属于行为型模式,使代码易于维护
  3. 比如 MVC 模式,C(Controller 控制器)是 M(Model 模型)和 V(View 视图)的中介者,在前后端交互时起到了中间人的作用

3.2. 原理类图

对原理类图的说明-即(中介者模式的角色及职责)

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

3.3. 代码实现

完成前面的智能家庭的项目,使用中介者模式

中介者模式一智能家庭的操作流程:

  1. 创建ConcreMediator对象
public abstract class Mediator {
//将给中介者对象,加入到集合中
public abstract void Register(String colleagueName, Colleague colleague);

//接收消息, 具体的同事对象发出
public abstract void GetMessage(int stateChange, String colleagueName);


public abstract void SendMessage();
}
  1. 创建各个同事类对象,比如: Alarm、CoffeeMachine. TV..
  2. 在创建同事类对象的时候,就直接通过构造器,加入到 colleagueMap
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 mediator;
    }
    
    public abstract void SendMessage(int stateChange);
}
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 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) {
        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!");
    }
    
}
  1. 同事类对象,可以调用sendMessage ,最终会去调用 ConcreteMediator的 getMessage方法
//具体的中介者类
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() {

    }


}
  1. getMessage会根据接收到的同事对象发出的消息来协调调用其它的同事对象 ,完成任务
  2. 可以看到getMessage是核心方法,完成相应任务
public class Client {
    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);
    }
}

3.4. 注意事项和细节

  1. 多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
  2. 减少类间依赖,降低了耦合,符合迪米特原则
  3. 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  4. 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

七、备忘录模式(游戏角色状态恢复问题)

1. 游戏角色状态恢复问题

游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战 Boss 后攻击力和防御力下

降,从备忘录对象恢复到大战前的状态

2. 传统方式

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

3. 备忘录模式

3.1. 简介

  1. 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
  2. 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
  3. 备忘录模式属于行为型模式

3.2. 原理

对原理类图的说明-即(备忘录模式的角色及职责)

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

3.3. 代码

① Originator 需要保存状态的对象

public class Originator {

    private String state;//状态信息

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    //编写一个方法,可以保存一个状态对象Memento
    // 因此编写一个方法,返回Memento
    public Memento saveStateMemento(){
        return new Memento(state);
    }

    //通过备忘录对象,恢复状态
    public String getStateFromMemento(Memento memento){
        state = memento.getState();
        return state;
    }

}

② Memento 备忘录对象 即Originator的内部状态

public class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}

③ Caretaker 守护者对象

public class Caretaker {

    //在list集合中会有很多的备忘录
    private List<Memento> mementoList = new ArrayList<>();

    public void add(Memento memento){
        mementoList.add(memento);
    }

    //获取到第index个Originator的备忘录对象
    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 血槽全满 ");
        //保存了当前的状态
        caretaker.add(originator.saveStateMemento());
        originator.setState(" 状态#2 血槽还剩80% ");
        caretaker.add(originator.saveStateMemento());
        originator.setState(" 状态#3 血槽还剩50% ");
        caretaker.add(originator.saveStateMemento());
        
        System.out.println("当前的状态是=="+originator.getState());
        //希望恢复到状态1
        System.out.println("恢复到状态1=="+originator.getStateFromMemento(caretaker.get(0)));
    }
}

游戏角色有攻击力和防御力,在大战 Boss 前保存自身的状态(攻击力和防御力),当大战 Boss 后攻击力和防御力下

降,从备忘录对象恢复到大战前的状态

备忘录对象(Originator内部状态)

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;
    }
    
}

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

public class GameRole {

    private int vit;
    private int def;

    public void setDef(int def) {
        this.def = def;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    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.println("游戏角色当前的攻击力:"+this.vit+",当前游戏角色的防御力:"+this.def);
    }


}

Caretaker: 守护者对象,负责保存多个备忘录对象

//守护者对象,保存游戏角色的状态
public class Caretaker {

    //对游戏角色保存状态
    private Memento memento;

    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();
        gameRole.setVit(100);
        gameRole.setDef(100);
        System.out.println("和boss大战前的状态");
        gameRole.display();
        //把当前状态保存caretaker
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(gameRole.createMemento());
        System.out.println("和boss开始大战");
        gameRole.setDef(30);
        gameRole.setVit(30);
        gameRole.display();
        System.out.println("大战后,使用备忘录恢复元气");
        gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
        gameRole.display();
    }
}

输出

和boss大战前的状态
游戏角色当前的攻击力:100,当前游戏角色的防御力:100
和boss开始大战
游戏角色当前的攻击力:30,当前游戏角色的防御力:30
大战后,使用备忘录恢复元气
游戏角色当前的攻击力:100,当前游戏角色的防御力:100

3.4. 注意事项和细节

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节
  3. 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
  4. 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理
  5. 为了节约内存,备忘录模式可以和原型模式配合使用

八、解释器模式(四则运算问题)

8.1 需求

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

  1. 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
  2. 在分别输入 a ,b, c, d, e 的值

8.2 传统方案解决四则运算问题分析

  1. 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
  2. 问题分析:如果加入新的运算符,比如 * / ( 等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱, 不够清晰.
  3. 解决方案:可以考虑使用解释器模式, 即: 表达式 -> 解释器(可以有多种) -> 结果

8.3 基本介绍

1、在编译原理中,一个算术表达式通过词法分析器形成词法单元,

而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。

这里的词法分析器和语法分析器都可以看做是解释器

2、解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,

使用该解释器来解释语言中的句子(表达式)

3、应用场景

  • 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
  • 一些重复出现的问题可以用一种简单的语言来表达
  • 一个简单语法需要解释的场景
  1. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等

8.4 原理

对原理类图的说明-即(解释器模式的角色及职责)

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

8.2 代码实现

  1. 应用实例要求 通过解释器模式来实现四则运算, 如计算 a+b-c 的值
  2. 思路分析和图解(类图)

① 抽象表达式

/**
 * @description 抽象类表达式,通过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);
}

② 具体表达式

/**
 * 抽象运算符号解析器这里,每个运算符号都只和自己左右两个数字有关系,
 * 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是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 VarExpression extends Expression {
    private String key; //key = a,key = b,key = c,

    public VarExpression(String key) {
        this.key = key;
    }

    //interpreter根据变量名称  返回对应值
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(key);
    }
}

③ 符号解释器

/**
 * 加法解释器
 */
public class AddExpression extends SymbolExpression {

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    //处理相加
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        //        返回left表达式对应的值        返回right表达式对应的值
        return super.left.interpreter(var)+super.right.interpreter(var);
    }
}

/**
 * @description
 */
public class SubExpression extends SymbolExpression {
    public SubExpression(Expression left, Expression right) {
        super(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;
    // 构造函数传参,并解析
    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();
    }

    public int run(HashMap<String, Integer> var) {
        //最后将表达式 a+b 和 var = {a=10,b=20}
        //然后传递给 expression 的 interpreter 进行解释执行
        return this.expression.interpreter(var);
    }

}

⑤ 客户端测试代码

/**
 * @description 解释器模式测试代码
 */
public class Client {

    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;
    }

}

输出

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

8.3 解释器模式的注意事项和细节

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

九、状态模式(APP 抽奖活动问题)

1. APP 抽奖活动问题

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

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

2.

状态模式

2.1. 简介

  1. 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
  2. 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

2.2. 原理类图

对原理类图的说明-即(状态模式的角色及职责)

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

2.3. 代码实现

  1. 应用实例要求 完成 APP 抽奖活动项目,使用状态模式.
  2. 思路分析和图解(类图) -定义出一个接口叫状态接口,每个状态都实现它。 -接口有扣除积分方法、抽奖方法、发放奖品方法

① 抽象状态(State角色)

public abstract class State {
    abstract void deduceMoney();//扣除积分 -50
    abstract boolean raffle();//是否抽中奖品
    abstract void dispensePrize();//发放奖品
}

② 具体状态 (ConcreteState角色)

public class CanRaffleState extends State {

    RaffleActivity activity;

    public CanRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }

    //已经扣除了积分,不能再扣
    @Override
    public void deduceMoney() {
        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 NoRaffleState extends State {

    // 初始化时传入活动引用,扣除积分后改变其状态
    RaffleActivity activity;

    public NoRaffleState(RaffleActivity activity) {
        this.activity = activity;
    }

    //当前状态是可以扣积分的,扣除后,将状态设置成抽象状态
    @Override
    public void deduceMoney() {
        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 DispenseState extends State {

    // 初始化时传入活动引用,发放奖品后改变其状态
    RaffleActivity activity;

    public DispenseState(RaffleActivity activity) {
        this.activity = activity;
    }


    @Override
    public void deduceMoney() {
        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);
        }

    }
}


public class DispenseOutState extends State {

    // 初始化时传入活动引用
    RaffleActivity activity;

    public DispenseOutState(RaffleActivity activity) {
        this.activity = activity;
    }


    @Override
    public void deduceMoney() {
        System.out.println("奖品发送完了,请下次再参加");
    }

    @Override
    public boolean raffle() {
        System.out.println("奖品发送完了,请下次再参加");
        return false;
    }

    @Override
    public void dispensePrize() {
        System.out.println("奖品发送完了,请下次再参加");
    }
}

③ 环境 (Context角色)

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.deduceMoney();
    }

    //抽奖
    public void raffle() {
        // 如果当前的状态是抽奖成功
        if (state.raffle()) {
            //领取奖品
            state.dispensePrize();
        }
    }


    public State getState() {
        return state;
    }


    public void setState(State state) {
        this.state = state;
    }

    //这里请大家注意,每领取一次奖品,count--
    public int getCount() {
        int curCount = count;
        count--;
        return curCount;
    }


    public void setCount(int count) {
        this.count = count;
    }


    public State getNoRafflleState() {
        return noRafflleState;
    }


    public void setNoRafflleState(State noRafflleState) {
        this.noRafflleState = noRafflleState;
    }


    public State getCanRaffleState() {
        return canRaffleState;
    }


    public void setCanRaffleState(State canRaffleState) {
        this.canRaffleState = canRaffleState;
    }


    public State getDispenseState() {
        return dispenseState;
    }


    public void setDispenseState(State dispenseState) {
        this.dispenseState = dispenseState;
    }

    public State getDispensOutState() {
        return dispensOutState;


    }


    public void setDispensOutState(State dispensOutState) {
        this.dispensOutState = dispensOutState;
    }
}

④ 客户端测试代码

public class Client {
    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();
        }
    }
}

2.4. 实际项目-借贷平台 源码剖析

简介
  1. 借贷平台的订单,有审核-发布-抢单 等等 步骤,随着操作的不同,会改变订单的状态, 项目中的这个模块实现就会使用到状态模式
  2. 使用状态模式完成 借贷平台项目的审核模块 [设计+代码]
代码

① 状态接口

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;
    }
}

② 具体状态及枚举

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;}

}


//各种具体状态类
class FeedBackState extends AbstractState {

    @Override
    public String getCurrentState() {
        return StateEnum.FEED_BACKED.getValue();
    }
}

public 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();
    }

}


public 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();
    }
    
}


public class PaidState extends AbstractState {
    @Override
    public void feedBackEvent(Context context) { context.setState(new FeedBackState());
    }
    
    @Override
    public String getCurrentState() {
        return StateEnum.PAID.getValue();
    }

}


public 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();
    }

}


public class ReviewState extends AbstractState{
    @Override
    public void makePriceEvent(Context context) { context.setState(new PublishState());
    }

    @Override
    public String getCurrentState() {
        return StateEnum.REVIEWED.getValue();
    }
}

③ 上下文环境

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) {
        // TODO Auto-generated method stub
        //创建 context 对象
        Context context = new Context();
        //将当前状态设置为 PublishState
        context.setState(new PublishState());
        System.out.println(context.getCurrentState());


        //		//publish --> not pay
        context.acceptOrderEvent(context);
        //		//not pay --> paid
        context.payOrderEvent(context);
        //	// 失败, 检测失败时,会抛出异常
        try {
            context.checkFailEvent(context);
            System.out.println("流程正常..");
        } catch (Exception e) {

            //	// TODO: handle exception
            //	System.out.println(e.getMessage());
            //	}

        }

    }
}

输出

当前状态 : PUBLISHED
PUBLISHED
当前状态 : NOT_PAY
当前状态 : PAID

2.5. 注意事项和细节

  1. 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
  2. 方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
  3. 符合“开闭原则”。容易增删状态
  4. 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
  5. 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候, 可以考虑使用状态模式

十、策略模式(鸭子问题)

1. 简介

编写鸭子项目,具体要求如下:

  1. 有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等)
  2. 显示鸭子的信息

传统方式解决问题类图

传统的方式实现的问题分析和解决方案

  1. 其它鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞了,这是不正确的
  2. 上面说的 1 的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
  3. 为了改进 1 问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决
  4. 问题又来了,如果我们有一个玩具鸭子 ToyDuck, 这样就需要 ToyDuck 去覆盖 Duck 的所有实现的方法 => 解决思路 -> 策略模式 (strategy pattern)

2. 策略模式

2.1. 简介

  1. 策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
  2. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)

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

2.2. 代码

① 行为接口

public interface FlyBehavior {
    void fly();
}

② 具体行为

public class GoodFlyBehavior implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("自由飞翔,飞翔能力max");
    }
}


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 class WildDuck extends Duck {

    public WildDuck() {
        flyBehavior = new GoodFlyBehavior();
    }

    @Override
    void display() {
        System.out.println("野鸭子");
    }

}


public class PekingDuck extends Duck {

    public PekingDuck() {
        flyBehavior = new BadFlyBehavior();
    }

    @Override
    void display() {
        System.out.println("北京鸭");
    }

}


public class ToyDuck extends Duck{

    public ToyDuck() {
        flyBehavior = new NoFlyBehavior();
    }

    @Override
    void display() {
        System.out.println("玩具鸭");
    }

}

④ 客户端测试

public class Client {
    public static void main(String[] args) {
        WildDuck wildDuck = new WildDuck();
        wildDuck.display();
        wildDuck.fly();
        PekingDuck pekingDuck = new PekingDuck();
        pekingDuck.display();
        pekingDuck.fly();
    }
}

2.3. 在 JDK-Arrays 应用的源码分析

JDK 的 Arrays 的 Comparator 就使用了策略模式

public class Strategy {
    public static void main(String[] args) {
        //数组
        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>() {
            public int compare(Integer o1, Integer o2) {
                if (o1 > o2) {
                    return -1;
                } else {
                    return 1;
                }
            }

            ;
        };

        // 说 明
        /*
        public static <T> void sort(T[] a, Comparator<? super T> c) {
            if (c == null) {
                sort(a);
            } else {
                if (LegacyMergeSort.userRequested)
                    legacyMergeSort(a, c);
                else
                    TimSort.sort(a, 0, a.length, c, null, 0, 0);
            }
        }
         */

        //方式 1
        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("data2=" + Arrays.toString(data2));

    }
}

在Android中,使用ListView时都需要设置一个Adapter,而这个Adapter根据我们实际的需求可以用ArrayAdapter、SimpleAdapter等

等,这里就运用到策略模式。

2.4. 注意事项和细节

  1. 策略模式的关键是:分析项目中变化部分与不变部分
  2. 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
  3. 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为) 即可,避免了使用多重转移语句(if..else if..else)
  4. 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
  5. 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞

十一、职责链模式(OA系统采购审批需求问题)

1. 需求

需求:学校 OA 系统的采购审批项目

采购员采购教学器材

  1. 如果金额 小于等于 5000, 由教学主任审批 (0<=x<=5000)
  2. 如果金额 小于等于 10000, 由院长审批 (5000<x<=10000)
  3. 如果金额 小于等于 30000, 由副校长审批 (10000<x<=30000)
  4. 如果金额 超过 30000 以上,有校长审批 ( 30000<x) 请设计程序完成采购审批项目

传统设计方案:

传统方案解决 OA 系统审批问题分析

  1. 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的 Approver (审批人)完成审批。
  2. 传统方式的问题分析 : 客户端这里会使用到 分支判断(比如 switch) 来对不同的采购请求处理, 这样就存在如下问题 (1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化 (2) 客户端必须明确的知道 有多少个审批级别和访问
  3. 这样 对一个采购请求进行处理 和 Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护
  4. 解决方案 => 职责链模式

2. 职责链模式

2.1. 简介

  1. 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
  2. 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
  3. 这种类型的设计模式属于行为型模式

对原理类图的说明-即(职责链模式的角色及职责)

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

2.2. 代码

① 编写请求 (Request角色)

public class PurchaseRequest {
    private int type = 0;
    private float price = 0.0f;
    private int id = 0;

    public PurchaseRequest(int id, int type, float price) {
        this.type = type;
        this.id = id;
        this.price = price;
    }

    public int getType() {
        return type;
    }

    public float getPrice() {
        return price;
    }

    public int getId() {
        return id;
    }
}

② 抽象的请求处理者 (Handler角色)

public abstract class Approver {

    protected Approver approver;//下一个处理者
    protected String name;//名字

    public Approver(String name) {
        this.name = name;
    }

    public void setApprover(Approver approver) {
        this.approver = approver;
    }

    abstract void processRequest(PurchaseRequest request);
}

③ 具体的请求处理者 (ConcreteHandler角色)

public class DepartmentApprover extends Approver{

    public DepartmentApprover(String name) {
        super(name);
    }


    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice()<=5000){
            System.out.println("请求编号id="+request.getId()+" 被 "+this.name+"处理");
        }else {
            System.out.println(this.name+"无法处理,交给上一级处理 -> "+approver.name);
            approver.processRequest(request);
        }
    }

}


public class CollegeApprover extends Approver{

    public CollegeApprover(String name) {
        super(name);
    }
    
    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice()>5000&&request.getPrice()<=10000){
            System.out.println("请求编号id="+request.getId()+" 被 "+this.name+"处理");
        }else {
            System.out.println(this.name+"无法处理,交给上一级处理 -> "+approver.name);
            approver.processRequest(request);
        }
    }
}

public class ViceSchoolMasterApprover extends Approver{

    public ViceSchoolMasterApprover(String name) {
        super(name);
    }

    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice()>10000&&request.getPrice()<=30000){
            System.out.println("请求编号id="+request.getId()+" 被 "+this.name+"处理");
        }else {
            System.out.println(this.name+"无法处理,交给上一级处理 -> "+approver.name);
            approver.processRequest(request);
        }
    }

}


public class SchoolMasterApprover extends Approver{

    public SchoolMasterApprover(String name) {
        super(name);
    }

    @Override
    void processRequest(PurchaseRequest request) {
        if (request.getPrice()>30000){
            System.out.println("请求编号id="+request.getId()+" 被 "+this.name+"处理");
        }else {
            System.out.println(this.name+"无法处理,交给下一级处理 -> "+approver.name);
            approver.processRequest(request);
        }
    }

}

④ 客户端调用

public class Client {
    public static void main(String[] args) {
    
        //创建一个审批请求
        PurchaseRequest request = new PurchaseRequest(1,1,13200);
        //创建相关的审批人
        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);

    }
}

输出

主任无法处理,交给上一级处理 -> 院长
院长无法处理,交给上一级处理 -> 副校长
请求编号id=1 被 副校长处理

2.3. 应用

Android中的事件分发机制就是类似于责任链模式。

另外,OKhttp中对请求的处理也是用到了责任链模式。

2.4. 注意事项和细节

  1. 将请求和处理分开,实现解耦,提高系统的灵活性
  2. 简化了对象,使对象不需要知道链的结构
  3. 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  4. 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
  5. 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中 Tomcat 对 Encoding 的处理、拦截器
;