栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

一、模板方法模式(模板模式)——钩子方法 1、需求-豆浆制作问题

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

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

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

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

原理

 说明:

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

抽象类

//抽象类,表示豆浆
public abstract class SoyaMilk {
    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {

        select();
        addCondiments();
        soak();
        beat();

    }

    //选材料
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆  ");
    }

    //添加不同的配料, 抽象方法, 子类具体实现
    abstract void addCondiments();

    //浸泡
    void soak() {
        System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
    }

    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
    }
}

实现抽象类的子类

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

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

客户端实现

public class Client {
    public static void main(String[] args) {
        //制作红豆豆浆

        System.out.println("----制作红豆豆浆----");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();
    }
}

输出

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

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

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

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

豆浆制作增加狗子函数

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

//抽象类,表示豆浆
public abstract class SoyaMilk {
    //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
    final void make() {

        select();
        if (needCondiments()) {
            addCondiments();
        }
        soak();
        beat();

    }

    //选材料
    void select() {
        System.out.println("第一步:选择好的新鲜黄豆  ");
    }

    //添加不同的配料, 抽象方法, 子类具体实现
    abstract void addCondiments();

    //狗子函数-是否需要添加配料
    boolean needCondiments(){
        return true;
    }

    //浸泡
    void soak() {
        System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
    }

    void beat() {
        System.out.println("第四步:黄豆和配料放到豆浆机去打碎  ");
    }
}

增加一个纯豆浆的子类

//纯豆浆
public class onlySoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {}

    @Override
    boolean needCondiments() {
        return false;
    }
}

Client 

public class Client {
    public static void main(String[] args) {
        //制作红豆豆浆

        System.out.println("----制作红豆豆浆----");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        System.out.println("----制作花生豆浆----");
        SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
        peanutSoyaMilk.make();


        System.out.println("----制作纯豆浆----");
        SoyaMilk onlySoyaMilk = new onlySoyaMilk();
        onlySoyaMilk.make();
    }
}

 输出

----制作红豆豆浆----
第一步:选择好的新鲜黄豆  
 第二步加入上好的红豆 
第三步, 黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
----制作花生豆浆----
第一步:选择好的新鲜黄豆  
 第二步加入上好的花生 
第三步, 黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
----制作纯豆浆----
第一步:选择好的新鲜黄豆  
第三步, 黄豆和配料开始浸泡, 需要3小时 
第四步:黄豆和配料放到豆浆机去打碎  
5、模板方法模式在 Spring 框架应用的源码分析

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

  • ConfigurableApplicationContext 接口中申明了一个模板方法 refresh() 。
  • AbstractApplicationContext 类实现了 ConfigurableApplicationContext 接口中的 refresh()  方法。它相当于我们例子中的抽象类 SyoaMilk。
  • AbstractApplicationContext 的refresh()函数体中使用了getBeanFactory() 、refreshBeanFactory() 、postProcessBeanFactory()、onReFresh()。getBeanFactory() 、refreshBeanFactory() 是抽象方法,postProcessBeanFactory()、onReFresh() 是钩子方法。
  • GenericApplicationContext 实现了getBeanFactory() 、refreshBeanFactory()。它相当于我们例子中的 抽象类子类PeanutSoyaMilk。
  • AbstractRefreshableApplicationContext 是另外一个抽象类子类。
  • ......
6、 模板方法模式的注意事项和细节
  • 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。

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

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

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

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

二、命令模式(源码讲解) 1、智能生活项目需求
  • 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app 就可以控制对这些家电工作。

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

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

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

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

2、命令模式介绍 基本介绍

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

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

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

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

命令模式原理

 说明:

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

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

类图如下所示

代码实现

创建命令接口

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

创建接收者(灯 - LightReceiver )

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

    public void off() {
        System.out.println(" 电灯关闭了.. ");
    }
}

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

public class LightonCommand implements Command {
    //聚合LightReceiver
    private LightReceiver lightReceiver;

    public LightonCommand(LightReceiver lightReceiver) {
        super();
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        //调用接收者的方法
        lightReceiver.on();
    }

    @Override
    public void undo() {
        // 调用接收者的方法
        lightReceiver.off();
    }
}

public class LightOffCommand implements Command {
    //聚合LightReceiver
    private LightReceiver lightReceiver;

    public LightOffCommand(LightReceiver lightReceiver) {
        super();
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        //调用接收者的方法
        lightReceiver.off();
    }

    @Override
    public void undo() {
        // 调用接收者的方法
        lightReceiver.on();
    }
}

创建一个空命令

public class NoCommand implements Command {
    @Override
    public void execute() {
    }

    @Override
    public void undo() {
    }
}

创建调用者(遥控器)

//调用者 - 遥控器
public class Invoker {
    // 开 按钮的命令数组
    Command[] onCommands;
    Command[] offCommands;
    // 执行撤销的命令
    Command undoCommand;

    // 构造器,完成对按钮初始化
    public Invoker() {
        onCommands = new Command[5];
        offCommands = new Command[5];

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

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

    // 按下开按钮
    public void onButtonWasPushed(int no) {
        // 找到你按下的开的按钮, 并调用对应方法
        onCommands[no].execute();
        // 记录这次的操作,用于撤销
        undoCommand = onCommands[no];
    }

    // 按下关按钮
    public void offButtonWasPushed(int no) {
        // 找到你按下的关的按钮, 并调用对应方法
        offCommands[no].execute();
        // 记录这次的操作,用于撤销
        undoCommand = offCommands[no];
    }

    // 按下撤销按钮
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}

 客户端

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

输出

--------按下灯的开按钮-----------
 电灯打开了.. 
--------按下灯的关按钮-----------
 电灯关闭了.. 
--------按下撤销按钮-----------
 电灯打开了.. 
添加电视开关命令 

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

电视命令接收者

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

    public void off() {
        System.out.println(" 电视关闭了.. ");
    }
}

 电视开、关命令

public class TvonCommand implements Command {
    //聚合LightReceiver
    private TvReceiver tvReceiver;

    public TvonCommand(TvReceiver tvReceiver) {
        super();
        this.tvReceiver = tvReceiver;
    }

    @Override
    public void execute() {
        //调用接收者的方法
        tvReceiver.on();
    }

    @Override
    public void undo() {
        // 调用接收者的方法
        tvReceiver.off();
    }
}

public class TvOffCommand implements Command {
    //聚合LightReceiver
    private TvReceiver tvReceiver;

    public TvOffCommand(TvReceiver tvReceiver) {
        super();
        this.tvReceiver = tvReceiver;
    }

    @Override
    public void execute() {
        //调用接收者的方法
        tvReceiver.off();
    }

    @Override
    public void undo() {
        // 调用接收者的方法
        tvReceiver.on();
    }
}

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

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

        System.out.println("=========使用遥控器操作电视机==========");
        TvReceiver tvReceiver = new TvReceiver();
        TvOffCommand tvOffCommand = new TvOffCommand(tvReceiver);
        TvonCommand tvonCommand = new TvonCommand(tvReceiver);
        //给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作
        invoker.setCommand(1, tvOnCommand, tvOffCommand);
        System.out.println("--------按下电视机的开按钮-----------");
        invoker.onButtonWasPushed(1);
        System.out.println("--------按下电视机的关按钮-----------");
        invoker.offButtonWasPushed(1);
        System.out.println("--------按下撤销按钮-----------");
        invoker.undoButtonWasPushed();
    }
}

输出

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

Process finished with exit code 0
4、命令模式在Spring框架 JdbcTemplate 的源码解析

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

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

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

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

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

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

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

  • 有一个命令接口,由子类各种命令(如电视打开命令)去实现,
  • 子类各种命令聚合了接收者(被调用者)的操作(如打开电视,打开灯光),
  • 遥控器(调用者)通过聚合命令接口来调用他的子类(相当于命令执行,因为子类是命令接口的子类又有被调用者的操作)。
  • 客户端要创建遥控器,必须有命令子类,必须要有接收者。
三、访问者模式-双分派 1、测评系统需求

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

2、传统方式解决测评系统需求
  • 定义抽象类 Person
  • Person子类为 Man、Women
  • Man、Women 中定义 不同的评价方法(成功、失败、待定)

传统方式问题分析

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

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

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

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

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

原理类图如下:

原理类图说明:

  • Visitor 是抽象访问者,为该对象结构中的 ConcreteElement 的每个类申明一个 visit 操作。
  • ConcreteVisitor:是一个具体的访问者,它实现Visitor声明的操作,是每个操作实现的部分。
  • ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素。
  • Element 定义一个accept方法,接收一个访问者对象。
  • ConcreteElement 为具体元素,实现了 accept 方法。
4、访问者模式解决测评系统需求 说明
  • 将人分为男人和女人,对歌手进行评测,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)。

代码实现 

抽象访问者和其具体实现

//访问者
public interface Action {
    void getManResult(Man man);
    void getWomenResult(Women man);
}

public class Success implements Action {
    @Override
    public void getManResult(Man man) {
        System.out.println(" 男人给的评价该歌手很成功 !");
    }

    @Override
    public void getWomenResult(Women man) {
        System.out.println(" 女人给的评价该歌手很成功 !");
    }
}

public class Failure implements Action {
    @Override
    public void getManResult(Man man) {
        System.out.println(" 男人给的评价该是歌手演唱失败 !");
    }

    @Override
    public void getWomenResult(Women man) {
        System.out.println(" 女人给的评价是该歌手演唱失败 !");
    }
}

Element 类和其具体实现

// 元素,接受对象
public abstract class Person {
    //提供一个方法,让访问者可以访问
    public abstract void accept(Action action);
}

public class Man extends Person {
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}

public class Women extends Person {
    @Override
    public void accept(Action action) {
        action.getWomenResult(this);
    }
}

数据结构类

//数据结构,维护很多人
public class ObjectStructure {
    //维护了一个集合
    private List personList = new linkedList();

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

    //移除
    public void detach(Person person) {
        personList.remove(person);
    }

    //显示测评
    public void display(Action action) {
        for (Person person : personList) {
            person.accept(action);
        }
    }
}

客户端

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

        System.out.println("===============");
        Failure failure = new Failure();
        objectStructure.display(failure);
    }
}

输出

 男人给的评价该歌手很成功 !
 女人给的评价该歌手很成功 !
===============
 男人给的评价该是歌手演唱失败 !
 女人给的评价是该歌手演唱失败 !
 增加一个待定的评价

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

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

    @Override
    public void getWomenResult(Women man) {
        System.out.println(" 女人给的评价是待定 !");
    }
}

客户端增加如下代码即可

        System.out.println("===============");
        Wait wait = new Wait();
        objectStructure.display(wait);
双分派说明

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

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

5、总结

优点:

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

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

缺点:

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

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

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

四、迭代器模式(iterator) 1、需求-展示学校院系结构

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

传统设计方案

传统方式问题分析

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

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

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

2、迭代器模式介绍 基本介绍

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

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

原理

原理类图说明:

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

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

持有的最小对象是 Department

public class Department {
    private String name;
    private String des;

    public Department(String name, String des) {
        this.name = name;
        this.des = des;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public String getName() {
        return name;
    }

    public String getDes() {
        return des;
    }
}

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

public interface Iterator {
    boolean hasNext();
    E next();
    void remove();
}

public class ComputerCollegeIterator implements Iterator {
    //这里我们以数组的形式存放数据
    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 Department next() {
        Department department = departments[position];
        position++;
        return department;
    }

    //默认空实现
    @Override
    public void remove() {

    }
}

public class InfoColleageIterator implements Iterator {
    //这里我们以List的形式存放数据
    private List departmentList;
    //索引
    private int position = -1;

    public InfoColleageIterator(List departmentList) {
        this.departmentList = departmentList;
    }

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

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

    //空实现remove
    @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() {
        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++;
    }

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

public class InfoCollege implements College {
    List departments;

    public InfoCollege() {
        departments = new ArrayList();
        addDepartment("信息安全专业", " 信息安全专业 ");
        addDepartment("网络安全专业", " 网络安全专业 ");
        addDepartment("服务器安全专业", " 服务器安全专业 ");
    }

    @Override
    public String getName() {
        return "信息学院";
    }

    @Override
    public void addDepartment(String name, String desc) {
        departments.add(new Department(name,desc));
    }

    @Override
    public Iterator createIterator() {
        return new InfoColleageIterator(departments);
    }
}

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

public class OutPutImpl {
    //学院集合
    List collegeList;

    public OutPutImpl(List collegeList) {

        this.collegeList = collegeList;
    }
    //遍历所有学院,然后调用printDepartment 输出各个学院的系
    public void printCollege() {

        //从collegeList 取出所有学院, Java 中的 List 已经实现Iterator
        java.util.Iterator iterator = collegeList.iterator();

        while(iterator.hasNext()) {
            //取出一个学院
            College college = iterator.next();
            System.out.println("=== "+college.getName() +"=====" );
            printDepartment(college.createIterator()); //得到对应迭代器
        }
    }

    //输出 学院输出 系
    public void printDepartment(Iterator iterator) {
        while(iterator.hasNext()) {
            Department d = (Department)iterator.next();
            System.out.println(d.getName());
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //创建学院
        List collegeList = new ArrayList();

        ComputerCollege computerCollege = new ComputerCollege();
        InfoCollege infoCollege = new InfoCollege();

        collegeList.add(computerCollege);
        collegeList.add(infoCollege);

        OutPutImpl outPutImpl = new OutPutImpl(collegeList);
        outPutImpl.printCollege();
    }
}

输出

=== 计算机学院=====
Java专业
PHP专业
大数据专业
=== 信息学院=====
信息安全专业
网络安全专业
服务器安全专业
4、迭代器模式在 JDK - ArrayList 集合应用的源码分析

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

优点:

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

缺点:

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

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

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

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

示意图如下

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

代码实现

第三方

public class CurrentConditions {
    // 温度,气压,湿度
    private float temperature;
    private float pressure;
    private float humidity;

    //更新 天气情况,是由 WeatherData 来调用,我使用推送模式
    public void update(float temperature, float pressure, float humidity) {
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    //显示
    public void display() {
        System.out.println("***Today mTemperature: " + temperature + "***");
        System.out.println("***Today mPressure: " + pressure + "***");
        System.out.println("***Today mHumidity: " + humidity + "***");
    }
}

核心类

public class WeatherData {
    private float temperatrue;
    private float pressure;
    private float humidity;
    //加入的第三方
    private CurrentConditions currentConditions;

    public WeatherData(CurrentConditions currentConditions) {
        this.currentConditions = currentConditions;
    }

    public void dataChange() {
        //调用 接入方的 update
        currentConditions.update(temperatrue, pressure, humidity);
    }

    //当数据有更新时,就调用 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);
    }
}

输出

***Today mTemperature: 30.0***
***Today mPressure: 150.0***
***Today mHumidity: 40.0***
============天气情况变化=============
***Today mTemperature: 40.0***
***Today mPressure: 160.0***
***Today mHumidity: 20.0***
问题分析
  • 其他第三方接入气象站获取数据的问题

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

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

3、观察者模式介绍

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

  • 奶站:Subject
  • 用户:Observer 

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

  • registerObserver 注册

  • removeObserver 移除

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

Observer:接收输入

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

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

类图如下

观察者

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


public class CurrentConditions implements Observer {
    // 温度,气压,湿度
    private float temperature;
    private float pressure;
    private float humidity;

    //更新 天气情况,是由 WeatherData 来调用,我使用推送模式
    @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=================");
        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 来调用,我使用推送模式
    @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("***Today mTemperature: " + temperature + "***");
        System.out.println("***Today mPressure: " + pressure + "***");
        System.out.println("***Today mHumidity: " + humidity + "***");
    }
}

主题

//接口,由 WeatherData 实现
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}


public class WeatherData implements Subject {
    private float temperatrue;
    private float pressure;
    private float humidity;
    //观察者集合
    private List observers;

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

    //当数据有更新时,就调用 setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //调用 notifyObservers, 将最新的信息 推送给 观察者集合
        notifyObservers();
    }

    //注册一个观察者
    @Override
    public void registerObserver(Observer o) {
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    //移除一个观察者
    @Override
    public void removeObserver(Observer o) {
        if (observers.contains(o)) {
            observers.remove(o);
        }
    }

    //遍历所有的观察者,并通知
    @Override
    public void notifyObservers() {
        for (Observer o:observers){
            o.update(this.temperatrue, this.pressure, this.humidity);
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        //创建 WeatherData
        WeatherData weatherData = new WeatherData();

        //创建观察者
        CurrentConditions currentConditions = new CurrentConditions();
        BaiduSite baiduSite = new BaiduSite();

        //注册到weatherData
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiduSite);

        //更新天气情况
        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(30, 150, 40);

        System.out.println("n移除 CurrentConditions 观察者n");
        weatherData.removeObserver(currentConditions);

        System.out.println("通知各个注册的观察者, 看看信息");
        weatherData.setData(30, 150, 40);
    }
}

输出

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

移除 CurrentConditions 观察者

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

观察者模式的好处:

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

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

5、观察者模式在Jdk源码中的应用
//jdk观察者接口
public interface Observer {
    void update(Observable o, Object arg);
}

//jdk 主题类
public class Observable {
    ...
    private Vector obs;

    

    public Observable() {
        obs = new Vector<>();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    
    public void notifyObservers() {
        notifyObservers(null);
    }
}

源码分析:

  •  Observable的作用和地位等价与前面讲过的 Subject
  • Observable 是类不是接口,类中以及实现了核心的方法,即管理 Observer 的方法 addObserver、deleteObserver、notifyObservers
  • Observer 地位等价我们前面讲过的 Observer,有 update 方法
  • Observable 和 Observer 的使用方法和前面讲过的一样,只是 Observable 是类,通过继承来实现观察者模式
六、中介者模式(Mediator Pattern) 1、需求-智能家电项目

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

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

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

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

问题分析:

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

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

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

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

原理类图

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

类图如下

中介者模式操作流程:

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

中介者类

//抽象中介者
public abstract class Mediator {
    //将同事对象加入到集合中
    public abstract void register(String colleagueName, Colleague colleague);
    //接收到消息,具体的同事对象发出
    public abstract void getMessage(int stateChange, String colleagueName);
    public abstract void sendMessage();
}

//具体的中介者类
public class ConcreteMediator extends Mediator {
    //集合,放入所有的同事对象
    private HashMap colleagueMap;
    private HashMap interMap;

    public ConcreteMediator() {
        colleagueMap = new HashMap();
        interMap = new HashMap();
    }

    @Override
    public void register(String colleagueName, Colleague colleague) {
        colleagueMap.put(colleagueName, colleague);

        // TODO Auto-generated method stub

        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) {
                //闹钟刚开始响,会自动打开电视机和咖啡机
                String coffeeName = interMap.get("CoffeeMachine");
                CoffeeMachine coffeeMachine = (CoffeeMachine) colleagueMap.get(coffeeName);
                coffeeMachine.startCoffee();
                String tvName = interMap.get("TV");
                Tv tv = (Tv) colleagueMap.get(tvName);
                tv.startTv();
            } else if (stateChange == 1) {
                //关闭闹钟,会自动关闭电视
                String tvName = interMap.get("TV");
                Tv tv = (Tv) colleagueMap.get(tvName);
                tv.stopTv();
            }
        } else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
            //收到咖啡冲完的消息会自动拉下窗帘
            String curtainsName = interMap.get("Curtains");
            Curtains curtains = (Curtains) colleagueMap.get(curtainsName);
            curtains.upCurtains();
        } else if (colleagueMap.get(colleagueName) instanceof Tv) {
            //如果是以TV发出的消息,这里处理...
        } else if (colleagueMap.get(colleagueName) instanceof Curtains) {
            //如果是以窗帘发出的消息,这里处理...
        }
    }

    @Override
    public void sendMessage() {

    }
}

同事类

//同事抽象类
public abstract class Colleague {
    private Mediator mediator;
    protected String name;

    public Colleague(Mediator mediator, String name) {
        this.mediator = mediator;
        this.name = name;
    }

    public Mediator getMediator() {
        return mediator;
    }

    public abstract void sendMessage(int stateChange);
}

public class Alarm extends Colleague {
    public Alarm(Mediator mediator, String name) {
        super(mediator, name);
        //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
        mediator.register(name, this);
    }

    public void sendAlarm(int stateChange) {
        sendMessage(stateChange);
    }

    @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);
        //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
        mediator.register(name, this);
    }

    public void startCoffee() {
        System.out.println(" 开始冲咖啡 ");
    }

    public void finishCoffee() {
        System.out.println(" 5分钟后 ");
        System.out.println(" 咖啡冲好了 ");
        sendMessage(0);
    }

    @Override
    public void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
}

public class Curtains extends Colleague {
    public Curtains(Mediator mediator, String name) {
        super(mediator, name);
        //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
        mediator.register(name, this);
    }

    public void upCurtains() {
        System.out.println("正在拉窗帘");
    }

    @Override
    public void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
}

public class Tv extends Colleague {
    public Tv(Mediator mediator, String name) {
        super(mediator, name);
        //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
        mediator.register(name, this);
    }

    public void startTv() {
        System.out.println("打开电视机");
    }

    public void stopTv() {
        System.out.println("关闭电视机");
    }

    @Override
    public void sendMessage(int stateChange) {
        this.getMediator().getMessage(stateChange, this.name);
    }
}

客户端

public class ClientTest {
    public static void main(String[] args) {
        //创建一个中介者对象
        Mediator mediator = new ConcreteMediator();
        //创建 Alarm 并且加入到 ConcreteMediator 的 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 , 并  且加入到  ConcreteMediator 对象的HashMap
        Tv tV = new Tv(mediator, "TV");

        //让闹钟发出消息
        alarm.sendAlarm(0);
        coffeeMachine.finishCoffee();
        alarm.sendAlarm(1);
    }
}

输出

 开始冲咖啡 
打开电视机
 5分钟后 
 咖啡冲好了 
正在拉窗帘
关闭电视机
5、总结
  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦

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

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

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

七、备忘录模式(Memento Pattern)

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

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

问题分析:

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

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

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

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

原理类图

说明:

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

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

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

代码实现

备忘录对象

public class Memento {
    private String state;

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

    public String getState() {
        return state;
    }
}

原始对象

public class Originator {
    private String state;

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

    public String getState() {
        return state;
    }

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

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

守护者对象

public class Caretaker {
    //list集合中有很多的备忘录对象
    private List mementos = new ArrayList<>();

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

    //取到第 index 个 Originator 的备忘录对象
    public Memento get(int index) {
        return mementos.get(index);
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();

        originator.setState(" 状态#1 攻击力 100 ");
        //保存了当前的状态
        caretaker.add(originator.saveMemento());

        originator.setState(" 状态#2 攻击力 80 ");
        caretaker.add(originator.saveMemento());

        originator.setState(" 状态#3 攻击力 50 ");
        caretaker.add(originator.saveMemento());

        System.out.println(" 当前的状态是:" + originator.getState());
        //希望恢复到状态1
        System.out.println(" 恢复到状态#1 ");
        originator.getFromMemento(caretaker.get(0));
        System.out.println("当前的状态是:" + originator.getState());
    }
}

输出

 当前的状态是: 状态#3 攻击力 50 
 恢复到状态#1 
当前的状态是: 状态#1 攻击力 100 
3、备忘录模式解决游戏角色恢复

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

原理类图

代码实现 

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

public class GameRole {
    //攻击力
    private int vit;
    //防御力
    private int def;

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

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

    public int getVit() {
        return vit;
    }

    public int getDef() {
        return def;
    }

    //将当前状态保存到 Memento,并返回 Memento
    public Memento saveToMemento() {
        return new Memento(vit,def);
    }

    //从备忘录恢复 GameRole 的状态
    public void restoreFromMemento(Memento memento) {
        this.vit = memento.getVit();
        this.def = memento.getDef();
    }

    //显示
    public void display() {
        System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);
    }
}

备忘录对象

public class Memento {
    //攻击力
    private int vit;
    //防御力
    private int def;

    public Memento(int vit, int def) {
        this.vit = vit;
        this.def = def;
    }

    public int getVit() {
        return vit;
    }

    public int getDef() {
        return def;
    }
}

守护者对象

public class Caretaker {
    //对于 GameRole 如果只保存一次状态
    private Memento memento;
    //对于 GameRole 如果保存多次状态
//    private List mementos = new ArrayList<>();
    //对多个游戏角色保存多个状态
//    private HashMap> rolesMementos = new HashMap<>();

    public void setMemento(Memento memento) {
        this.memento = memento;
    }

    //取到第 index 个 Originator 的备忘录对象
    public Memento getMemento() {
        return memento;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        //创建游戏角色
        GameRole gameRole = new GameRole();
        gameRole.setVit(100);
        gameRole.setDef(100);

        System.out.println("和boss大战前的状态");
        gameRole.display();

        //把当前状态保存caretaker
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(gameRole.saveToMemento());

        System.out.println("和boss大战~~~");
        gameRole.setDef(30);
        gameRole.setVit(30);
        gameRole.display();

        System.out.println("大战后,使用备忘录对象恢复到对战前");

        gameRole.restoreFromMemento(caretaker.getMemento());
        System.out.println("恢复后的状态");
        gameRole.display();
    }
}

输出

和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到对战前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100
4、总结 
  • 备忘录模式给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 适用的场景:后悔药;打游戏时的存档;Windows 里的 ctrl + z;IE 中的后退;数据库的事物管理。
  • 为了节约内存,备忘录模式可以和原型模式配合使用。 
八、解释器模式(Interpreter Pattern)

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

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

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

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

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

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

2、解释器模式介绍 基本介绍

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

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

应用场景:

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

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

原理类图

说明:

  • Context:是环境角色,含有解释器之外的全局信息。
  • Abstractexpression:抽象表达式,声明一个抽象的解释操作,这个方法被抽象语法树中所有节点共享。
  • Terminalexpression:为终结符表达式,实现与文法中的终结符相关的解释操作
  • NonTerminalexpression:为非终结符表达式,为文法中的非终结符实现的解释操作
  • 说明:输入信息通过 Client 输入即可。
3、解释器模式解决 四则运算问题 原理类图

代码实现

解释器类

public abstract class expression {
    
    public abstract int interpreter(Map var);
}


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(Map var) {
        return var.get(key);
    }
}


public class Addexpression extends Symbolexpression {

    public Addexpression(expression left, expression right) {
        super(left, right);
    }

    //处理相加,var仍然是 {a=10.b=20}
    @Override
    public int interpreter(Map var) {
        return super.left.interpreter(var) + super.right.interpreter(var);
    }
}


public class Subexpression extends Symbolexpression {

    public Subexpression(expression left, expression right) {
        super(left, right);
    }

    //处理相减,var仍然是 {a=10.b=20}
    @Override
    public int interpreter(Map var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }
}

Context类

public class Calculator {
    //定义表达式
    private expression expression;

    //构造器函数,传参并解析
    //expStr = a+b
    public Calculator(String expStr) {
        //安排运算先后顺序
        Stack stack = new Stack();
        //表达式差分成字符数组 [a,+,b]
        char[] charArr = expStr.toCharArray();

        expression left;
        expression right;
        //遍历我们的字符数组,即遍历 [a,+,b]
        //针对不同的情况做处理
        for (int i=0;i var) {
        //最后将表达式和var绑定,比如{a=10,b=20}
        //然后传递给 expression的 interpreter 进行解释执行
        return this.expression.interpreter(var);
    }
}

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

客户端类

public class Client {
    public static void main(String[] args) throws IOException {
        String expStr = getExpStr();
        Map map = getValue(expStr);
        Calculator calculator = new Calculator(expStr);
        int result = calculator.run(map);
        System.out.println("运算结果:" + expStr + " = " +result);
    }

    //获得表达式
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        String expStr = new BufferedReader(new InputStreamReader(System.in)).readLine();
        return expStr;
    }

    //获得值映射
    public static Map getValue(String expStr){
        Map map = new HashMap();
        for (char ch:expStr.toCharArray()) {
            if ((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')) {
                System.out.print("请输入" + ch + "的值:");
                Scanner scanner = new Scanner(System.in);
                String var = scanner.nextLine();
                map.put(String.valueOf(ch), Integer.valueOf(var));
            }
        }
        return map;
    }
}

 输出:

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

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

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

首先要引入 spring-expression 的依赖

        
            org.springframework
            spring-expression
            4.3.7.RELEASE
        

测试类

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

类图如下

说明:

  • expression 接口,是解释器的接口,下面有不同的实现类,比如 Spelexpression、CompositeStringexpression。
  • 在我们使用的时候,会根据不同的 Parse 对象,返回不同的 expression 对象 ( parser.parseexpression() )。
5、解释器模式总结
  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树。此时,就可以考虑使用解释器模式,让程序具有良好的扩展性。
  • 应用场景:编译器、运算表达式计算、正则表达式、机器人等。
  • 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低。
九、状态模式(State Pattern) 1、需求 - app抽奖活动

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

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

2、状态模式介绍 基本介绍

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

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

原理类图

说明:

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

说明:

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

抽象状态及其子类

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


public class NoRaffleState extends State {
    private Activity activity;

    // 初始化时传入活动引用,扣除积分后改变其状态
    public NoRaffleState(Activity activity) {
        this.activity = activity;
    }

    @Override
    public void deductMoney() {
        System.out.println("扣除50积分成功,您可以抽奖了");
        activity.setState(activity.getCanRaffleState());
    }

    @Override
    public boolean raffle() {
        throw new RuntimeException("扣了积分才能抽奖喔!");
    }

    @Override
    public void dispensePrize() {
        throw new RuntimeException("不能发放奖品");
    }
}



public class CanRaffleState extends State{
    Activity activity;

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

    @Override
    public void deductMoney() {
        throw new RuntimeException("已经扣除过了积分,不能在扣除");
    }

    @Override
    public boolean raffle() {
        System.out.println("正在抽奖,请稍候!");
        Random r = new Random();
        int num = r.nextInt(10);
        //10%的中奖几率
        if (num == 0) {
            //改变状态为发布奖品
            activity.setState(activity.getDispenseState());
            return true;
        } else {
            System.out.println("很遗憾没有抽中奖");
            //改变状态为不能抽奖
            activity.setState(activity.getNoRaffleState());
            return false;
        }
    }

    @Override
    public void dispensePrize() {
        throw new RuntimeException("没中奖不能发放奖品");
    }
}


public class DispenseState extends State {
    private Activity activity;
    public DispenseState(Activity activity) {
        this.activity = activity;
    }

    @Override
    public void deductMoney() {
        throw new RuntimeException("不能扣除积分");
    }

    @Override
    public boolean raffle() {
        throw new RuntimeException("不能抽奖");
    }

    @Override
    public void dispensePrize() {
        if (activity.getCount() <= 0) {
            System.out.println("很遗憾,奖品发放玩了");
            activity.setState(activity.getDispenseOutState());
            //退出程序
            System.exit(0);
            return;
        }
        System.out.println("恭喜,中奖了");
        // 奖品发放完毕后,改变状态为不能抽奖
        activity.setState(activity.getNoRaffleState());
    }
}


public class DispenseOutState extends State {
    private Activity activity;

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

    @Override
    public void deductMoney() {
        throw new RuntimeException("奖品发送完了,请下次再参加");
    }

    @Override
    public boolean raffle() {
        throw new RuntimeException("奖品发送完了,请下次再参加");
    }

    @Override
    public void dispensePrize() {
        throw new RuntimeException("奖品发送完了,请下次再参加");
    }
}

Context-环境角色类

public class Activity extends State {
    //表示当前的状态,是变化的
    State state = null;
    //奖品数量
    int count = 0;

    //四个属性,表示四种状态
    State noRaffleState = new NoRaffleState(this);
    State canRaffleState = new CanRaffleState(this);
    State dispenseState = new DispenseState(this);
    State dispenseOutState = new DispenseOutState(this);

    //构造器
    //1、初始化当前的状态为 NoRaffleState(即不能抽奖的状态)
    //2、初始化奖品的数量
    public Activity(int count) {
        this.count = count;
        this.state = noRaffleState;
    }

    //扣分, 调用当前状态的 deductMoney
    @Override
    public void deductMoney() {
        state.deductMoney();
    }

    @Override
    public boolean raffle() {
        throw new RuntimeException("Activity中 该方法不能被调用");
    }

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

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

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

    public State getNoRaffleState() {
        return noRaffleState;
    }

    public State getCanRaffleState() {
        return canRaffleState;
    }

    public State getDispenseState() {
        return dispenseState;
    }

    public State getDispenseOutState() {
        return dispenseOutState;
    }
}

Client 客户端

public class Client {
    public static void main(String[] args) {
        // 创建活动对象,奖品有1个奖品
        Activity activity = new Activity(1);

        for (int i=0;i<100;i++) {
            System.out.println("--------第" + (i + 1) + "次抽奖----------");
            //参加抽奖,第一步扣除积分
            activity.deductMoney();
            //第二部抽奖
            activity.dispensePrize();
        }
    }
}

其中一次的输出结果

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

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

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

传统方式

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

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

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

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

订单流程的状态图

设计的大致类图

State 接口,定义了各个行为

public interface State {
    
    void checkEvent(Context context);
    
    void checkFailEvent(Context context);
    
    void makePriceEvent(Context context);
    
    void acceptOrderEvent(Context context);
    
    void noPeopleAcceptOrderEvent(Context context);
    
    void payOrderEvent(Context context);
    
    void orderFailEvent(Context context);
    
    void feedBackEvent(Context context);
    
    String getCurrentState();
}

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

public class AbstractState implements State {
    protected static final RuntimeException EXCEPTION = new RuntimeException();

    @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 noPeopleAcceptOrderEvent(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void payOrderEvent(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void orderFailEvent(Context context) {
        throw EXCEPTION;
    }

    @Override
    public void feedBackEvent(Context context) {
        throw EXCEPTION;
    }

    @Override
    public String getCurrentState() {
        throw EXCEPTION;
    }
}

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

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 "通用状态";
    }
}

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

    @Override
    public String getCurrentState() {
        return "审核状态";
    }
}

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

    @Override
    public void noPeopleAcceptOrderEvent(Context context) {
        context.setState(new FeedBackState());
    }

    @Override
    public String getCurrentState() {
        return "发布状态";
    }
}

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 "未支付状态";
    }
}

public class PaidState extends AbstractState {
    @Override
    public void feedBackEvent(Context context) {
        context.setState(new FeedBackState());
    }

    @Override
    public String getCurrentState() {
        return "已支付状态";
    }
}

public class FeedBackState extends AbstractState {
    @Override
    public String getCurrentState() {
        return "已完结状态";
    }
}

 Context 环境角色

//环境上下文,实现 State的所有方法,每次调用后输出当前状态信息
public class Context extends AbstractState {
    State state = null;//当前状态

    @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 noPeopleAcceptOrderEvent(Context context) {
        state.noPeopleAcceptOrderEvent(this);
        getCurrentState();
    }

    @Override
    public void payOrderEvent(Context context) {
        state.payOrderEvent(this);
        getCurrentState();
    }

    @Override
    public void orderFailEvent(Context context) {
        state.orderFailEvent(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 Client {
    public static void main(String[] args) {
        //创建 Context 对象
        Context context = new Context();
        //将当前状态设置为 PublishState 状态
        context.setState(new PublishState());
        context.getCurrentState();//输出当前状态
        context.acceptOrderEvent(context);//接收订单
        context.payOrderEvent(context);//付款
        context.feedBackEvent(context);//完结
    }
}

输出 

当前状态:发布状态
当前状态:未支付状态
当前状态:已支付状态
当前状态:已完结状态
5、状态模式总结

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

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

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

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

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

十、策略模式(Strategy Pattern) 1、需求 - 编写鸭子项目

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

2、显示鸭子的信息

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

代码实现
public abstract class Duck {
    //显示鸭子信息
    public abstract void display();

    public void quack() {
        System.out.println("鸭子嘎嘎叫");
    }

    public void swim() {
        System.out.println("鸭子会游泳~~");
    }

    public void fly() {
        System.out.println("鸭子会飞~~");
    }
}

public class WildDuck extends Duck {
    @Override
    public void display() {
        System.out.println("~~~野鸭~~~");
    }
}

public class BeijingDuck extends Duck {
    @Override
    public void display() {
        System.out.println("~~~北京鸭~~~");
    }

    //因为北京鸭不能飞翔,因此需要重写fly
    @Override
    public void fly() {
        System.out.println("北京鸭不能飞翔");
    }
}

public 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("玩具鸭不会飞翔~~~");
    }
}
问题分析
  1. 其他鸭子,都继承了 Duck 类,所以 fly 让所有子类都会飞,这是不正确的。

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

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

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

3、策略模式介绍 基本介绍

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

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

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

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

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

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

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

策略接口 - 飞翔

public interface FlyBehavior {
    void fly();
}

public class GoodFlyBehavior implements FlyBehavior {
    @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 interface QuackBehavior {
    void quack();
}

public class GeGeQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println(" 咯咯叫 ");
    }
}

public class GagaQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println(" 嘎嘎叫 ");
    }
}

public class NoQuackBehavior implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println(" 不会叫 ");
    }
}

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

public abstract class Duck {
    //飞翔属性,策略接口
    protected FlyBehavior flyBehavior;
    //叫声属性,策略接口
    protected QuackBehavior quackBehavior;

    public Duck() {
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    //显示鸭子信息
    public abstract void display();

    public void quack() {
        if (quackBehavior != null) {
            quackBehavior.quack();
        }
    }

    public void swim() {
        System.out.println("鸭子会游泳~~");
    }

    public void fly() {
        if (flyBehavior != null) {
            flyBehavior.fly();
        }
    }
}

public class WildDuck extends Duck {
    //构造器,传入 FlyBehavior 的对象
    public WildDuck() {
        super();
        flyBehavior = new GoodFlyBehavior();
        quackBehavior = new GagaQuackBehavior();
    }

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

public class BeijingDuck extends Duck {
    public BeijingDuck() {
        super();
        flyBehavior = new NoFlyBehavior();
        quackBehavior = new GeGeQuackBehavior();
    }

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

public class ToyDuck extends Duck {
    public ToyDuck() {
        super();
        flyBehavior = new NoFlyBehavior();
        quackBehavior = new NoQuackBehavior();
    }

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

Client 客户端

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

        ToyDuck toyDuck = new ToyDuck();
        toyDuck.display();
        toyDuck.fly();
        toyDuck.quack();

        BeijingDuck beijingDuck = new BeijingDuck();
        beijingDuck.display();
        beijingDuck.fly();
        beijingDuck.quack();
        System.out.println("北京鸭实际飞翔能力");
        beijingDuck.setFlyBehavior(new BadFlyBehavior());
        beijingDuck.fly();
    }
}
5、策略模式在 JDK-Arrays 应用的源码分析

Arrays 的 Comparator 使用了策略模式。

如下Demo:

public class Strategy {
    public static void main(String[] args) {
        Integer[] data = {9,1,2,8,4,3};

        Comparator comparator = new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 > o2) {
                    return 1;
                } else {
                    return -1;
                }
            }
        };
        //
        Arrays.sort(data, comparator);
        System.out.println(Arrays.toString(data));
    }
}

说明:

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

再看下 Arrays.sort() 的源码

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

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

        Comparator comparator = new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 > o2) {
                    return -1;
                } else {
                    return 1;
                }
            }
        };
6、策略模式总结

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

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

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

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

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

十一、责任链模式(职责链模式)(Chain of Responsibility Pattern) 1、需求 - 学校OA系统的采购审批

采购员采购教学器材

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

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

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

问题:

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

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

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

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

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

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

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

原理类图

说明:

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

代码实现

请求类

public class PurchaseRequest {
    private int id;
    private int type;
    private float price;

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

    public int getId() {
        return id;
    }

    public int getType() {
        return type;
    }

    public float getPrice() {
        return price;
    }
}

抽象处理者

public abstract class Approver {
    Approver approver;
    String name;

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

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

    //处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
    public abstract void processRequest(PurchaseRequest request);
}

具体处理者

public class DepartmentApprover extends Approver {
    public DepartmentApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getPrice() <= 5000) {
            System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");
        } else {
            approver.processRequest(request);
        }
    }
}

public class CollegeApprover extends Approver {
    public CollegeApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 5000 && request.getPrice() <= 10000) {
            System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");
        } else {
            approver.processRequest(request);
        }
    }
}


public class ViceSchoolApprover extends Approver {
    public ViceSchoolApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 10000 && request.getPrice() <= 30000) {
            System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");
        } else {
            approver.processRequest(request);
        }
    }
}

public class SchoolApprover extends Approver {
    public SchoolApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getPrice() > 30000) {
            System.out.println("请求编号 id=" + request.getId() + " 被 " + this.name + " 处理");
        } else {
            approver.processRequest(request);
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //创建一个请求
        PurchaseRequest purchaseRequest = new PurchaseRequest(1, 1, 111000);

        //创建相关的审批人
        DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
        CollegeApprover collegeApprover = new CollegeApprover("李院长");
        ViceSchoolApprover viceSchoolApprover = new ViceSchoolApprover("王副校");
        SchoolApprover schoolApprover = new SchoolApprover("佟校长");


        //需要将各个审批级别的下一个设置好 (处理人构成环形: )
        departmentApprover.setApprover(collegeApprover);
        collegeApprover.setApprover(viceSchoolApprover);
        viceSchoolApprover.setApprover(schoolApprover);
        schoolApprover.setApprover(departmentApprover);

        departmentApprover.processRequest(purchaseRequest);
        viceSchoolApprover.processRequest(purchaseRequest);
    }
}
5、职责链模式在 SpringMVC 框架应用的源码分析

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

//SpringMVC 的核心处理类
public class DispatcherServlet extends frameworkServlet {
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //声明HandlerExecutionChain对象
		HandlerExecutionChain mappedHandler = null;
        ...
		//获取到HandlerExecutionChain 对象
		mappedHandler = getHandler(processedRequest);
		if (mappedHandler == null) {
			noHandlerFound(processedRequest, response);
			return;
		}
        ...
        //在mappedHandler.applyPreHandle 内部得到了HandlerInterceptor interceptor
        //调用了拦截器的interceptor.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];
public class HandlerExecutionChain {
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}
}

说明:

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

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

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

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/424294.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号