23种设计模式 - 个人笔记(三)

目录

行为型模式

12、模板方法模式

12.1、模式的定义与特点

12.2、模式的结构与实现

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

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

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

13、命令模式

13.1、命令模式的定义与特点

13.2、命令模式的结构与实现

13.3、命令模式在Spring框架JdbcTemplate应用的源码分析

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

14、访问者模式

14.1、模式的定义与特点

14.2、模式的结构与实现

14.3、访问者模式应用实例

14.4、访问者模式的注意事项和细节

15、迭代器模式

15.1、模式的定义与特点

15.2、模式的结构与实现

15.3、迭代器模式应用实例

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

15.5、迭代器模式的注意事项和细节

16、观察者模式

16.1、模式的定义与特点

16.2、模式的结构与实现

16.3、应用案例

16.4、观察者模式在Jdk应用的源码分析

17、中介者模式

17.1、模式的定义与特点

17.2、模式的结构与实现

17.3、中介者模式应用实例-智能家庭管理

17.4、中介者模式的注意事项和细节

18、备忘录模式

18.1、模式的定义与特点

18.2、模式的结构与实现

18.3、游戏角色恢复状态实例

18.4、备忘录模式的注意事项和细节

19、解释器模式

19.1、模式的定义与特点

19.2、模式的结构与实现

19.3、模式的应用实例

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

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

20、状态模式

20.1、状态模式的定义与特点

20.2、状态模式的结构与实现

20.3、状态模式的应用实例

20.4、状态模式的注意事项和细节

21、策略模式

21.1、策略模式的定义与特点

21.2、策略模式的结构与实现

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

21.4、策略模式的注意事项和细节

21.5、策略模式的应用场景

22、责任链模式(职责链模式)

22.1、模式的定义与特点

22.2、模式的结构与实现

22.3、模式的应用实例

22.4、模式的应用场景

22.5、模式的扩展

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

22.7、职责链模式的注意事项和细节


前言

学习文档:


 行为型模式

12、模板方法模式


在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。

以下介绍的模板方法模式将解决以上类似的问题。

12.1、模式的定义与特点

模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。

  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下。

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

12.2、模式的结构与实现

模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。

1. 模式的结构

模板方法模式包含以下主要角色。

1)抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

2)具体子类/具体实现(Concrete Class)

具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

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

1) AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法operationr2,3,4

2) ConcreteClass 实现抽象方法operationr2,3,4, 以完成算法中特点子类的步骤

案例

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

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

思路分析

代码实现

// 抽象类,表示豆浆
public abstract class SoyaMilk {

    //模板方法,使用final修饰,不让子类去覆盖
    final void make(){
        select();
        addCondiments();
        soak();
        beat();
    }

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

    //添加不同的配料
    abstract void addCondiments();

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

    void beat(){
        System.out.println("第四步,黄豆和配料放到豆浆机打碎");
    }
}
public class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的红豆");
    }
}
public class PeanutSoyMilk 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 peanutSoyMilk = new PeanutSoyMilk();
        peanutSoyMilk.make();
    }
}

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

在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。

// 抽象类,表示豆浆
public abstract class SoyaMilk {

    //模板方法,使用final修饰,不让子类去覆盖
    final void make(){
        select();
        if (customerWantCondiments()){
            addCondiments();
        }
        soak();
        beat();
    }

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

    //添加不同的配料
    abstract void addCondiments();

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

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

    //钩子方法,决定是否需要添加配料
    boolean customerWantCondiments(){
        return true;
    }
}
public class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
        System.out.println("加入上好的红豆");
    }
}
public class PeanutSoyMilk extends SoyaMilk {

    @Override
    void addCondiments() {
        System.out.println("加入上好的花生");
    }
}
public class PureSoyaMilk extends SoyaMilk {
    @Override
    void addCondiments() {
       //空实现
    }

    @Override
    boolean customerWantCondiments() {
        return false;
    }
}
public class Client {
    public static void main(String[] args) {
        //制作红豆豆浆
        System.out.println("---制作红豆豆浆---");
        SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
        redBeanSoyaMilk.make();

        //制作花生豆浆
        System.out.println("---制作花生豆浆---");
        SoyaMilk peanutSoyMilk = new PeanutSoyMilk();
        peanutSoyMilk.make();

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

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

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

2) 代码分析+角色分析+说明类图

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

1)、基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
2)、实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

3)、既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。

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

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

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

13、命令模式


在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。

13.1、命令模式的定义与特点

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

命令模式的主要优点如下。

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。


其缺点是:

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

13.2、命令模式的结构与实现

可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。

1. 模式的结构

命令模式包含以下主要角色。

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

案例

智能生活项目需求:我们买了一套智能家电,与照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装一个app就可以控制这些家电工作。

思路分析

代码实现

public interface Command {

    //执行动作(有“打开”、“关闭”两个动作)
    void execute();

    //撤销动作
    void undo();
}
public class LightReceiver {

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

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

    //聚合LightReceiver
    LightReceiver lightReceiver;

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

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

    @Override
    public void undo() {
        // 调用接收者的方法
        lightReceiver.off();
    }
}
public class LightOffCommand implements Command  {
    LightReceiver lightReceiver;//聚合LightReceiver

    public LightOffCommand(LightReceiver lightReceiver){
        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 RemoteController {
    //按钮的命令数组
    Command[] onCommands;
    Command[] offCommands;

    //执行撤销命令
    Command undoCommand;

    public RemoteController() {
        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(int no) {
        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);
        //需要一个遥控器
        RemoteController remoteController = new RemoteController();
        //给遥控器设置命令,比如no-0是电灯的开和关的操作
        remoteController.setCommand(0,lightOnCommand,lightOffCommand);

        System.out.println("按下灯的开按钮");
        remoteController.onButtonWasPushed(0);
        System.out.println("按下灯的关按钮");
        remoteController.offButtonWasPushed(0);
        System.out.println("按下灯的撤销按钮");
        remoteController.undoButtonWasPushed(0);
    }
}

13.3、命令模式在Spring框架JdbcTemplate应用的源码分析

1) Spring框架的JdbcTemplate就使用到了命令模式

2) 代码分析

3) 模式角色分析说明
StatementCallback 接口 ,类似命令接口(Command)
-
class QueryStatementCallback implements StatementCallback<T>, SqlProvider , 匿名内
部类, 实现了命令接口, 同时也充当命令接收者
-
命令调用者 是 JdbcTemplate , 其中 execute(StatementCallback<T> action) 方法中,调
action.doInStatement 方法 . 不同的 实现 StatementCallback 接口的对象,对应不同
doInStatemnt 实现逻辑
- 另外实现 StatementCallback 命令接口的子类还有 QueryStatementCallback

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

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

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

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

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

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

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

14、访问者模式


在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。

这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

14.1、模式的定义与特点

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下。

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

14.2、模式的结构与实现

访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。

1. 模式的结构

访问者模式包含以下主要角色。

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

                   图1 访问者(Visitor)模式的结构图

应用案例

14.3、访问者模式应用实例

1) 应用实例要求

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

2) 思路分析和图解(类图)

3) 代码实现

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

}
public class Man extends Person {
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}
/**
 * 说明
 * 1、这里我们使用到了双分派,即在首先客户端程序中,将具体的状态作为参数传递到WoMan中(第一次分派)
 * 2、然后WoMan这个类调用作为参数的“具体方法”中方法get,通过将自己(this)作为参数传入,完成第二次的分派
 */
public class WoMan extends Person {
    @Override
    public void accept(Action action) {
        action.getWomanResult(this);
    }
}
public abstract class Action {
    // 得到男性的测评
    public abstract void getManResult(Man man);

    // 得到女的测评
    public abstract void getWomanResult(WoMan woman);
}
public class Success extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男人给的评价是该歌手很成功");
    }

    @Override
    public void getWomanResult(WoMan woman) {
        System.out.println("女人给的评价是该歌手很成功");
    }
}
public class Fail extends Action {
    @Override
    public void getManResult(Man man) {
        System.out.println("男人给这个歌手评价是失败");
    }

    @Override
    public void getWomanResult(WoMan woman) {
        System.out.println("女人给这个歌手评价是失败");
    }
}
// 数据结构,管理很多人(Man, WoMan)
public class ObjectStructure {
    // 维护了一个集合
    private List<Person> persons = new LinkedList<>();

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

    // 移除
    public void detach(Person p){
        persons.remove(p);
    }

    // 显示测评的情况
    public void display(Action action){
        for (Person person : persons) {
            person.accept(action);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new Man());
        objectStructure.attach(new WoMan());

        // 成功
        Success success = new Success();
        objectStructure.display(success);

        System.out.println("--------------------");

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

 

4) 应用案例的小结

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

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

14.4、访问者模式的注意事项和细节

优点

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

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

缺点

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

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

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

15、迭代器模式


在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。

既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

  1. 暴露了聚合类的内部表示,使其数据不安全;
  2. 增加了客户的负担。


“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。

迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。

15.1、模式的定义与特点

迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 遍历任务交由迭代器完成,这简化了聚合类。
  3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
  4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。

其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。

在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。

15.2、模式的结构与实现

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。现在我们来分析其基本结构与实现方法。

1. 模式的结构

迭代器模式主要包含以下角色。

  1. 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  2. 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  4. 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

其结构图如图 1 所示。

                          图1 迭代器模式的结构图

import java.util.*;
public class IteratorPattern {
    public static void main(String[] args) {
        Aggregate ag = new ConcreteAggregate();
        ag.add("中山大学");
        ag.add("华南理工");
        ag.add("韶关学院");
        System.out.print("聚合的内容有:");
        Iterator it = ag.getIterator();
        while (it.hasNext()) {
            Object ob = it.next();
            System.out.print(ob.toString() + "\t");
        }
        Object ob = it.first();
        System.out.println("\nFirst:" + ob.toString());
    }
}
//抽象聚合
interface Aggregate {
    public void add(Object obj);
    public void remove(Object obj);
    public Iterator getIterator();
}
//具体聚合
class ConcreteAggregate implements Aggregate {
    private List<Object> list = new ArrayList<Object>();
    public void add(Object obj) {
        list.add(obj);
    }
    public void remove(Object obj) {
        list.remove(obj);
    }
    public Iterator getIterator() {
        return (new ConcreteIterator(list));
    }
}
//抽象迭代器
interface Iterator {
    Object first();
    Object next();
    boolean hasNext();
}
//具体迭代器
class ConcreteIterator implements Iterator {
    private List<Object> list = null;
    private int index = -1;
    public ConcreteIterator(List<Object> list) {
        this.list = list;
    }
    public boolean hasNext() {
        if (index < list.size() - 1) {
            return true;
        } else {
            return false;
        }
    }
    public Object first() {
        index = 0;
        Object obj = list.get(index);
        ;
        return obj;
    }
    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = list.get(++index);
        }
        return obj;
    }
}

程序运行结果如下:

聚合的内容有:中山大学    华南理工    韶关学院   
First:中山大学

15.3、迭代器模式应用实例

1) 应用实例要求

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

成,一个学校有多个学院,一个学院有多个系。

2) 设计思路分析

3) 代码实现

public class Department {

	private String name;
	private String desc;
	public Department(String name, String desc) {
		super();
		this.name = name;
		this.desc = desc;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDesc() {
		return desc;
	}
	public void setDesc(String desc) {
		this.desc = desc;
	}
}
public class ComputerCollegeIterator implements Iterator {

	// 这里我们需要知道Department 是以怎样的方式存放的
	Department[] departments;
	int position = 0; // 遍历的位置

	
	public ComputerCollegeIterator(Department[] departments) {
		this.departments = departments;
	}

	//判断是否还有下一个元素
	@Override
	public boolean hasNext() {
		if(position >= departments.length || departments[position] == null) {
			return false;
		}else {
		
			return true;
		}
	}

	@Override
	public Object next() {
		Department department = departments[position];
		position += 1;
		return department;
	}
	
	// 删除的方法默认空实现
	public void remove() {
		
	}

}
public class InfoColleageIterator implements Iterator {
	
	List<Department> departmentList; // 信息工程学院以list的方式存放系
	int index = -1;// 索引
	
	public InfoColleageIterator(List<Department> departmentList) {
		this.departmentList = departmentList;
	}

	// 判断list中还有没有下一个元素
	@Override
	public boolean hasNext() {
		if(index >= departmentList.size() - 1) {
			return false;
		} else {
			index += 1;
			return true;
		}
	}

	@Override
	public Object next() {
		return departmentList.get(index);
	}
	
	// 空实现remove
	public void remove() {
		
	}

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

	Department[] departments;

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

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

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

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

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

}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class InfoCollege implements College {

	List<Department> departmentList;

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

	public InfoCollege() {
		departmentList = new ArrayList<Department>();
		addDepartment("信息安全专业", " 信息安全专业 ");
		addDepartment("网路安全专业", " 网路安全专业 ");
	}

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

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

}
import java.util.Iterator;
import java.util.List;

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

	public OutPutImpl(List<College> collegeList) {
		
		this.collegeList = collegeList;
	}

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


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

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

}

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

1) JDKArrayList 集合中就使用了迭代器模式

2) 代码分析+类图+说明

3) 角色分析说明

- 内部类Itr 充当具体实现迭代器Iterator 的类, 作为ArrayList 内部类

- List 就是充当了聚合接口,含有一个iterator() 方法,返回一个迭代器对象

- ArrayList 是实现聚合接口List 的子类,实现了iterator()

- Iterator 接口系统提供

- 迭代器模式解决了 不同集合(ArrayList ,LinkedList) 统一遍历问题

15.5、迭代器模式的注意事项和细节

优点

1) 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。

2) 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。

3) 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。

4) 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

缺点

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

16、观察者模式


在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。

在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。

16.1、模式的定义与特点

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

观察者模式是一种对象行为型模式,其主要优点如下。

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

16.2、模式的结构与实现

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

1. 模式的结构

观察者模式的主要角色如下。

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的结构图如图 1 所示。

                                      图1 观察者模式的结构图

2. 模式的实现

观察者模式的实现代码如下:

import java.util.*;
public class ObserverPattern {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Observer obs1 = new ConcreteObserver1();
        Observer obs2 = new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}

// 抽象观察者
interface Observer {
    void response(); //反应
}
 
// 具体观察者1
class ConcreteObserver1 implements Observer {
    public void response() {
        System.out.println("具体观察者1作出反应!");
    }
}

// 具体观察者2
class ConcreteObserver2 implements Observer {
    public void response() {
        System.out.println("具体观察者2作出反应!");
    }
}

// 抽象目标
abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();
    // 增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }
    // 删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    // 通知观察者方法
    public abstract void notifyObserver(); 
}

// 具体目标
class ConcreteSubject extends Subject {
    public void notifyObserver() {
        System.out.println("具体目标发生改变...");
        System.out.println("--------------");
        for (Object obs : observers) {
            ((Observer) obs).response();
        }
    }
}

程序运行结果如下:

具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!

16.3、应用案例

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

1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)

2) 需要设计开放型API,便于其他第三方也能接入气象站获取数据。

3) 提供温度、气压和湿度的接口

4) 测量数据更新时,要能实时的通知给第三方

传统解决方式

WeatherData
通过对气象站项目的分析,我们可以初步设计出一个 WeatherData

CurrentConditions(当前的天气情况)

可以理解成是我们气象局的网站 //推送

说明:

1) 通过getXxx方法,可以让第三方接入,并得到相关信息.

2) 当数据有更新时,气象站通过调用dataChange() 去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

public class CurrentConditions {

    // 温度、气压、湿度
    private float temperature;

    private float pressure;

    private float humidity;

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

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

* 类是核心类
* 1、包含最新的天气情况信息
* 2、含有 CurrentConditions 对象
* 3、数据有更新就主动调用 CurrentConditions对象的update(含display)方法,这样他们(接入方)就看到最新的信息
 

public class WeatherData {
    private float temperature;

    private float pressure;

    private float humidity;

    private CurrentConditions cuurentConditions;

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

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public CurrentConditions getCuurentConditions() {
        return cuurentConditions;
    }

    public void setCuurentConditions(CurrentConditions cuurentConditions) {
        this.cuurentConditions = cuurentConditions;
    }

    public void dataChange(){
        // 调用接入方的update
        cuurentConditions.update(getTemperature(), getPressure(), getHumidity());
    }

    // 当数据有更新时,就调用setData
    public void setData(float temperature, float pressure, float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        // 调用dataChange, 将最新的数据推送给接入方cuurentConditions
        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,50);
    }
}

问题分析

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

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

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

//WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange, 不利于维护,也不是动态加入

public void dataChange() {

       currentConditions.update(getTemperature(), getPressure(), getHumidity());

}

观察者模式解决天气预报需求

思路分析

代码实现

// 观察者接口,有观察者来实现
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("==========百度网站===========");
        System.out.println("百度网站 温度" + temperature + "***");
        System.out.println("百度网站 气压" + pressure + "***");
        System.out.println("百度网站 湿度" + humidity + "***");
    }
}
// 接口,让WeatherData 来实现
public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

* 类是核心类
 * 1、包含最新的天气情况信息
 * 2、含有 观察者集合,使用ArrayList管理
 * 3、数据有更新就主动调用 ArrayList,通知他们(接入方)就看到最新的信息 

/**
 * 类是核心类
 * 1、包含最新的天气情况信息
 * 2、含有 观察者集合,使用ArrayList管理
 * 3、数据有更新就主动调用 ArrayList,通知他们(接入方)就看到最新的信息
 */
public class WeatherData implements Subject {
    private float temperature;

    private float pressure;

    private float humidity;

    // 观察者集合
    private ArrayList<Observer> observers;

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

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }


    public void dataChange(){
        // 调用接入方的update
        notifyObservers();
    }

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

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

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

    // 遍历所有的观察者并通知
    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            observers.get(i).update(this.temperature, 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
        weatherData.registerObserver(baiduSite);

        // 测试
        System.out.println("通知各个注册的观察者,看看信息");
        weatherData.setData(10,100,50);

    }
}

观察者模式的好处

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

2) 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则。

16.4、观察者模式在Jdk应用的源码分析

1) JdkObservable类就使用了观察者模式

2) 代码分析+模式角色分析

3) 模式角色分析

- Observable 的作用和地位等价于 我们前面讲过Subject

- Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理Observer的方法 add.. delete .. notify...

- Observer 的作用和地位等价于我们前面讲过的 Observer, 有update

- Observable Observer 的使用方法和前面讲过的一样,只是Observable 是类,通过继承来实现观察者模式

17、中介者模式


在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。

如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。

在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。

17.1、模式的定义与特点

中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式是一种对象行为型模式,其主要优点如下。

  1. 类之间各司其职,符合迪米特法则。
  2. 降低了对象之间的耦合性,使得对象易于独立地被复用。
  3. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

17.2、模式的结构与实现

中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析。

1. 模式的结构

中介者模式包含以下主要角色。

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  2. 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

中介者模式的结构图如图 1 所示。

                                              图1 中介者模式的结构图

2. 模式的实现

中介者模式的实现代码如下:

// 抽象同事类
abstract class Colleague {
    protected Mediator mediator;
    public void setMedium(Mediator mediator) {
        this.mediator = mediator;
    }
    public abstract void receive();
    public abstract void send();
}

// 具体同事类
class ConcreteColleague1 extends Colleague {
    public void receive() {
        System.out.println("具体同事类1收到请求。");
    }
    public void send() {
        System.out.println("具体同事类1发出请求。");
        mediator.relay(this); //请中介者转发
    }
}

// 具体同事类
class ConcreteColleague2 extends Colleague {
    public void receive() {
        System.out.println("具体同事类2收到请求。");
    }
    public void send() {
        System.out.println("具体同事类2发出请求。");
        mediator.relay(this); //请中介者转发
    }
}


// 抽象中介者
abstract class Mediator {
    public abstract void register(Colleague colleague);
    public abstract void relay(Colleague cl); //转发
}

// 具体中介者
class ConcreteMediator extends Mediator {
    private List<Colleague> colleagues = new ArrayList<Colleague>();
    public void register(Colleague colleague) {
        if (!colleagues.contains(colleague)) {
            colleagues.add(colleague);
            colleague.setMedium(this);
        }
    }
    public void relay(Colleague cl) {
        for (Colleague ob : colleagues) {
            if (!ob.equals(cl)) {
                ((Colleague) ob).receive();
            }
        }
    }
} 

public class MediatorPattern {
    public static void main(String[] args) {
        Mediator md = new ConcreteMediator();
        Colleague c1, c2;
        c1 = new ConcreteColleague1();
        c2 = new ConcreteColleague2();
        md.register(c1);
        md.register(c2);
        c1.send();
        System.out.println("-------------");
        c2.send();
    }
}

程序的运行结果如下:

具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。

17.3、中介者模式应用实例-智能家庭管理

智能家庭项目:

1) 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘 等

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

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

2) 思路分析和图解(类图)

3) 代码实现

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

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

	public abstract void SendMessage();
}
// 具体的中介者类
public class ConcreteMediator extends Mediator {
	// 集合,放入所有同事对象
	private HashMap<String, Colleague> colleagueMap;
	private HashMap<String, String> interMap;

	public ConcreteMediator() {
		colleagueMap = new HashMap<String, Colleague>();
		interMap = new HashMap<String, String>();
	}

	@Override
	public void Register(String colleagueName, Colleague colleague) {
		// TODO Auto-generated method stub
		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) {
				((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() {
		// TODO Auto-generated method stub

	}

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

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

	public Mediator GetMediator() {
		return this.mediator;
	}

	public abstract void SendMessage(int stateChange);
}
// 具体的同事类
public class Alarm extends Colleague {

	// 构造器
	public Alarm(Mediator mediator, String name) {
		super(mediator, name);
		// 在创建Alarm同事对象时,将自己放入ConcreteMediator 对象中[集合]
		mediator.Register(name, this);
	}

	public void SendAlarm(int stateChange) {
		SendMessage(stateChange);
	}

	@Override
	public void SendMessage(int stateChange) {
		// 调用中介者对象的getMessage
		this.GetMediator().GetMessage(stateChange, this.name);
	}

}
public class Curtains extends Colleague {

	public Curtains(Mediator mediator, String name) {
		super(mediator, name);
		// TODO Auto-generated constructor stub
		mediator.Register(name, this);
	}

	@Override
	public void SendMessage(int stateChange) {
		// TODO Auto-generated method stub
		this.GetMediator().GetMessage(stateChange, this.name);
	}

	public void UpCurtains() {
		System.out.println("I am holding Up Curtains!");
	}

}
public class TV extends Colleague {

	public TV(Mediator mediator, String name) {
		super(mediator, name);
		// TODO Auto-generated constructor stub
		mediator.Register(name, this);
	}

	@Override
	public void SendMessage(int stateChange) {
		// TODO Auto-generated method stub
		this.GetMediator().GetMessage(stateChange, this.name);
	}

	public void StartTv() {
		// TODO Auto-generated method stub
		System.out.println("It's time to StartTv!");
	}

	public void StopTv() {
		// TODO Auto-generated method stub
		System.out.println("StopTv!");
	}
}
public class ClientTest {

	public static void main(String[] args) {
		// 创建一个中介者对象
		Mediator mediator = new ConcreteMediator();
		
		// 创建一个Alarm 并且加入 ConcreteMediator 对象的HashMap
		Alarm alarm = new Alarm(mediator, "alarm");
		
		// 创建一个CoffeeMachine 并且加入 ConcreteMediator 对象的HashMap
		CoffeeMachine coffeeMachine = new CoffeeMachine(mediator,
				"coffeeMachine");
		
		// 创建一个 Curtains , 并且加入 ConcreteMediator 对象的HashMap
		Curtains curtains = new Curtains(mediator, "curtains");
		TV tV = new TV(mediator, "TV");
		
		// 让闹钟发出消息
		alarm.SendAlarm(0);
		coffeeMachine.FinishCoffee();
		alarm.SendAlarm(1);
	}

}

17.4、中介者模式的注意事项和细节

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

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

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

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

18、备忘录模式


每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。

其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。

18.1、模式的定义与特点

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。


其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

18.2、模式的结构与实现

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。

1. 模式的结构

备忘录模式的主要角色如下。

  1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

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 String getState() {
        return state;
    }

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

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

    // 通过备忘录对象,恢复状态
    public void getStateFromMemento(Memento memento){
        state = memento.getState();
    }
}
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 攻击力100分");
        // 保存了当前状态
        caretaker.add(originator.saveStateMemento());

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

        originator.setState("状态2 攻击力50分");
        caretaker.add(originator.saveStateMemento());

        System.out.println("当前状态是=" + originator.getState());

        // 希望恢复到状态1 将originator 恢复到状态1
        originator.getStateFromMemento(caretaker.get(0));

        System.out.println("恢复到状态1,当前状态是=" + originator.getState());
    }
}

18.3、游戏角色恢复状态实例

1) 应用实例要求

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

2) 思路分析和图解(类图)

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 void setVit(int vit) {
        this.vit = vit;
    }

    public int getDef() {
        return def;
    }

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

    private int vit;

    private int def;

    public int getVit() {
        return vit;
    }

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

    public int getDef() {
        return def;
    }

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

    // 创建Memento,即根据当前的状态得到Memento
    public Memento createMemento(){
        return new Memento(vit, def);
    }

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

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

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

    // 如果只保存一次状态
    private Memento memento;

    // 对GameRole 保存多次状态
//    private ArrayList<Memento> mementos;

    // 对多个游戏角色保存多个状态
//    private HashMap<String, ArrayList<Memento>> roleMemento;


    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.setVit(30);
        gameRole.setDef(30);

        gameRole.display();

        System.out.println("大战后使用备忘录对象恢复到大战前");
        gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
        System.out.println("恢复后的状态");
        gameRole.display();
    }
}

18.4、备忘录模式的注意事项和细节

1) 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态

2) 实现了信息的封装,使得用户不需要关心状态的保存细节

3) 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意

4) 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3Windows 里的 ctri + z。4IE 中的后退。 5、数据库的事务管理

5) 为了节约内存,备忘录模式可以和原型模式配合使用

19、解释器模式

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。

19.1、模式的定义与特点

解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。

这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。

解释器模式是一种类行为型模式,其主要优点如下。

  1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。

解释器模式的主要缺点如下。

  1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。

19.2、模式的结构与实现

解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。

1) 文法

文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。

〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉7大学生I筱霞I英语
〈动词〉::=是|学习


注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

2) 句子

句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。

3) 语法树

语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。图 1 所示是“我是大学生”的语法树。

 

           图1 句子“我是大学生”的语法树

有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。

1. 模式的结构

解释器模式包含以下主要角色。

  1. 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  2. 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  3. 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  4. 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  5. 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。


解释器模式的结构图如图 2 所示。

                                               图2 解释器模式的结构图

2. 模式的实现

解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树,其代码结构如下:

//抽象表达式类
interface AbstractExpression {
    public void interpret(String info);    //解释方法
}
//终结符表达式类
class TerminalExpression implements AbstractExpression {
    public void interpret(String info) {
        //对终结符表达式的处理
    }
}
//非终结符表达式类
class NonterminalExpression implements AbstractExpression {
    private AbstractExpression exp1;
    private AbstractExpression exp2;
    public void interpret(String info) {
        //非对终结符表达式的处理
    }
}
//环境类
class Context {
    private AbstractExpression exp;
    public Context() {
        //数据初始化
    }
    public void operation(String info) {
        //调用相关表达式类的解释方法
    }
}

19.3、模式的应用实例

【例1】用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。

说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。

分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。

<expression> ::= <city>的<person>
<city> ::= 韶关|广州
<person> ::= 老人|妇女|儿童

然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。

  • 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
  • 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
  • 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
  • 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如图 3 所示。

                          图3 “韶粵通”公交车读卡器程序的结构图

1、抽象表达式类

interface Expression {
    public boolean interpret(String info);
}

2、终结符表达式类

class TerminalExpression implements Expression {
    private Set<String> set = new HashSet<String>();
    public TerminalExpression(String[] data) {
        for (int i = 0; i < data.length; i++) 
             set.add(data[i]);
    }
    public boolean interpret(String info) {
        if (set.contains(info)) {
            return true;
        }
        return false;
    }
}

3、非终结符表达式类

class AndExpression implements Expression {
    private Expression city = null;
    private Expression person = null;
    public AndExpression(Expression city, Expression person) {
        this.city = city;
        this.person = person;
    }
    public boolean interpret(String info) {
        String s[] = info.split("的");
        return city.interpret(s[0]) && person.interpret(s[1]);
    }
}

4、环境类

class Context {
    private String[] citys = {"韶关", "广州"};
    private String[] persons = {"老人", "妇女", "儿童"};
    private Expression cityPerson;
    public Context() {
        Expression city = new TerminalExpression(citys);
        Expression person = new TerminalExpression(persons);
        cityPerson = new AndExpression(city, person);
    }
    public void freeRide(String info) {
        boolean ok = cityPerson.interpret(info);
        if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
        else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
    }
}
/*文法规则
  <expression> ::= <city>的<person>
  <city> ::= 韶关|广州
  <person> ::= 老人|妇女|儿童
*/
public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Context bus = new Context();
        bus.freeRide("韶关的老人");
        bus.freeRide("韶关的年轻人");
        bus.freeRide("广州的妇女");
        bus.freeRide("广州的儿童");
        bus.freeRide("山东的儿童");
    }
}

程序运行结果如下:

您是韶关的老人,您本次乘车免费!
韶关的年轻人,您不是免费人员,本次乘车扣费2元!
您是广州的妇女,您本次乘车免费!
您是广州的儿童,您本次乘车免费!
山东的儿童,您不是免费人员,本次乘车扣费2元!

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

1) Spring框架中 SpelExpressionParser就使用到解释器模式

2) 代码分析+Debug源码

3) 说明

- Expression 接口 表达式接口

- 下面有不同的实现类,比如SpelExpression, 或者CompositeStringExpression

- 使用时候,根据你创建的不同的Parser 对象,返回不同的 Expression 对象

public Expression parseExpression(String expressionString, ParserContext context)
throws ParseException {
if (context == null) {
context = NON_TEMPLATE_PARSER_CONTEXT;
}
if (context.isTemplate()) {
return parseTemplate(expressionString, context); //返回的就是 CompositeStringExpression
}
else {
return doParseExpression(expressionString, context); //返回的就是 SpelExpression
}
}

- 使用得当 Expression对象,调用getValue 解释执行 表达式,最后得到结果

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

1) 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性

2) 应用场景:编译器、运算表达式计算、正则表达式、机器人等

3) 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.

20、状态模式

在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

20.1、状态模式的定义与特点

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式是一种对象行为型模式,其主要优点如下。

  1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。


状态模式的主要缺点如下。

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

20.2、状态模式的结构与实现

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。

1. 模式的结构

状态模式包含以下主要角色。

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  2. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

其结构图如图 1 所示。

                                             图1 状态模式的结构图

2. 模式的实现

状态模式的实现代码如下:

1、环境类

class Context {
    private State state;
    //定义环境类的初始状态
    public Context() {
        this.state = new ConcreteStateA();
    }
    //设置新状态
    public void setState(State state) {
        this.state = state;
    }
    //读取状态
    public State getState() {
        return (state);
    }
    //对请求做处理
    public void Handle() {
        state.Handle(this);
    }
}

2、抽象状态类

abstract class State {
    public abstract void Handle(Context context);
}

3、具体状态A类

class ConcreteStateA extends State {
    public void Handle(Context context) {
        System.out.println("当前状态是 A.");
        context.setState(new ConcreteStateB());
    }
}

4、具体状态B类

class ConcreteStateB extends State {
    public void Handle(Context context) {
        System.out.println("当前状态是 B.");
        context.setState(new ConcreteStateA());
    }
}
public class StatePatternClient {
    public static void main(String[] args) {
        Context context = new Context();    //创建环境      
        context.Handle();    //处理请求
        context.Handle();
        context.Handle();
        context.Handle();
    }
}

程序运行结果如下:

当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.

20.3、状态模式的应用实例

例1】用“状态模式”设计一个学生成绩的状态转换程序。

分析:本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。

首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态名属性和当前分数属性,以及加减分方法 addScore(intx) 和检查当前状态的抽象方法 checkState()。

然后,定义“不及格”状态类 LowState、“中等”状态类 MiddleState 和“优秀”状态类 HighState,它们是具体状态类,实现 checkState() 方法,负责检査自己的状态,并根据情况转换。

最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法 add(int score),客户类通过该方法来改变成绩状态。图 2 所示是其结构图。

                              图2 学生成绩的状态转换程序的结构图

程序代码如下:

 1、环境类

class ScoreContext {
    private AbstractState state;
    ScoreContext() {
        state = new LowState(this);
    }
    public void setState(AbstractState state) {
        this.state = state;
    }
    public AbstractState getState() {
        return state;
    }
    public void add(int score) {
        state.addScore(score);
    }
}

2、抽象状态类

abstract class AbstractState {
    protected ScoreContext hj;  //环境
    protected String stateName; //状态名
    protected int score; //分数
    public abstract void checkState(); //检查当前状态
    public void addScore(int x) {
        score += x;
        System.out.print("加上:" + x + "分,\t当前分数:" + score);
        checkState();
        System.out.println("分,\t当前状态:" + hj.getState().stateName);
    }
}

3、具体状态类:不及格

class LowState extends AbstractState {
    public LowState(ScoreContext h) {
        hj = h;
        stateName = "不及格";
        score = 0;
    }
    public LowState(AbstractState state) {
        hj = state.hj;
        stateName = "不及格";
        score = state.score;
    }
    public void checkState() {
        if (score >= 90) {
            hj.setState(new HighState(this));
        } else if (score >= 60) {
            hj.setState(new MiddleState(this));
        }
    }
}

3.2、具体状态类:中等

class MiddleState extends AbstractState {
    public MiddleState(AbstractState state) {
        hj = state.hj;
        stateName = "中等";
        score = state.score;
    }
    public void checkState() {
        if (score < 60) {
            hj.setState(new LowState(this));
        } else if (score >= 90) {
            hj.setState(new HighState(this));
        }
    }
}

3.3、具体状态类:优秀

class HighState extends AbstractState {
    public HighState(AbstractState state) {
        hj = state.hj;
        stateName = "优秀";
        score = state.score;
    }
    public void checkState() {
        if (score < 60) {
            hj.setState(new LowState(this));
        } else if (score < 90) {
            hj.setState(new MiddleState(this));
        }
    }
}
public class ScoreStateTest {
    public static void main(String[] args) {
        ScoreContext account = new ScoreContext();
        System.out.println("学生成绩状态测试:");
        account.add(30);
        account.add(40);
        account.add(25);
        account.add(-15);
        account.add(-25);
    }
}

程序运行结果如下:

学生成绩状态测试:
加上:30分,    当前分数:30分,    当前状态:不及格
加上:40分,    当前分数:70分,    当前状态:中等
加上:25分,    当前分数:95分,    当前状态:优秀
加上:-15分,    当前分数:80分,    当前状态:中等
加上:-25分,    当前分数:55分,    当前状态:不及格

20.4、状态模式的注意事项和细节

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

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

3) 符合“开闭原则”。容易增删状态

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

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

21、策略模式

在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。

在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。

21.1、策略模式的定义与特点

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

策略模式的主要优点如下。

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。


其主要缺点如下。

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类,增加维护难度。

21.2、策略模式的结构与实现

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。

1. 模式的结构

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。


其结构图如图 1 所示。
 

策略模式的结构图
                                                                图1 策略模式的结构图

2. 模式的实现

策略模式的实现代码如下:

    public class StrategyPattern {
        public static void main(String[] args) {
            Context c = new Context();
            Strategy s = new ConcreteStrategyA();
            c.setStrategy(s);
            c.strategyMethod();
            System.out.println("-----------------");
            s = new ConcreteStrategyB();
            c.setStrategy(s);
            c.strategyMethod();
        }
    }
    //抽象策略类
    interface Strategy {
        public void strategyMethod();    //策略方法
    }
    //具体策略类A
    class ConcreteStrategyA implements Strategy {
        public void strategyMethod() {
            System.out.println("具体策略A的策略方法被访问!");
        }
    }
    //具体策略类B
    class ConcreteStrategyB implements Strategy {
        public void strategyMethod() {
            System.out.println("具体策略B的策略方法被访问!");
        }
    }
    //环境类
    class Context {
        private Strategy strategy;
        public Strategy getStrategy() {
            return strategy;
        }
        public void setStrategy(Strategy strategy) {
            this.strategy = strategy;
        }
        public void strategyMethod() {
            strategy.strategyMethod();
        }
    }

程序运行结果如下:

具体策略A的策略方法被访问!
-----------------
具体策略B的策略方法被访问!

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

21.4、策略模式的注意事项和细节

1) 策略模式的关键是:分析项目中变化部分与不变部分

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

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

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

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

21.5、策略模式的应用场景

策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。

  1. 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  2. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  3. 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  4. 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
  5. 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

22、责任链模式(职责链模式)

在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。

在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSPServlet 的 Filter 等,所有这些,都可以考虑使用责任链模式来实现。

22.1、模式的定义与特点

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

注意:责任链模式也叫职责链模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

责任链模式是一种对象行为型模式,其主要优点如下。

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。


其主要缺点如下。

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

22.2、模式的结构与实现

通常情况下,可以通过数据链表来实现职责链模式的数据结构

1. 模式的结构

职责链模式主要包含以下角色。

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。


责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

其结构图如图 1 所示。客户端可按图 2 所示设置责任链。

                                                                      图1 责任链模式的结构图

                                                         图2 责任链

2. 模式的实现

职责链模式的实现代码如下:

public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //组装责任链
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交请求
        handler1.handleRequest("two");
    }
}
//抽象处理者角色
abstract class Handler {
    private Handler next;
    public void setNext(Handler next) {
        this.next = next;
    }
    public Handler getNext() {
        return next;
    }
    //处理请求的方法
    public abstract void handleRequest(String request);
}
//具体处理者角色1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具体处理者1负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}
//具体处理者角色2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具体处理者2负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}

程序运行结果如下:

具体处理者2负责处理该请求!

在上面代码中,我们把消息硬编码为 String 类型,而在真实业务中,消息是具备多样性的,可以是 int、String 或者自定义类型。因此,在上面代码的基础上,可以对消息类型进行抽象 Request,增强了消息的兼容性。

22.3、模式的应用实例

【例1】用责任链模式设计一个请假条审批模块。

分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。

首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。图 3 所示是其结构图。

                                               图3 请假条审批模块的结构图

程序代码如下:

public class LeaveApprovalTest {
    public static void main(String[] args) {
        //组装责任链
        Leader teacher1 = new ClassAdviser();
        Leader teacher2 = new DepartmentHead();
        Leader teacher3 = new Dean();
        //Leader teacher4=new DeanOfStudies();
        teacher1.setNext(teacher2);
        teacher2.setNext(teacher3);
        //teacher3.setNext(teacher4);
        //提交请求
        teacher1.handleRequest(8);
    }
}
//抽象处理者:领导类
abstract class Leader {
    private Leader next;
    public void setNext(Leader next) {
        this.next = next;
    }
    public Leader getNext() {
        return next;
    }
    //处理请求的方法
    public abstract void handleRequest(int LeaveDays);
}
//具体处理者1:班主任类
class ClassAdviser extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 2) {
            System.out.println("班主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者2:系主任类
class DepartmentHead extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 7) {
            System.out.println("系主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者3:院长类
class Dean extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 10) {
            System.out.println("院长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者4:教务处长类
class DeanOfStudies extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 20) {
            System.out.println("教务处长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

程序运行结果如下:

院长批准您请假8天。

假如增加一个教务处长类,可以批准学生请假 20 天,也非常简单,代码如下:

    //具体处理者4:教务处长类
    class DeanOfStudies extends Leader {
        public void handleRequest(int LeaveDays) {
            if (LeaveDays <= 20) {
                System.out.println("教务处长批准您请假" + LeaveDays + "天。");
            } else {
                if (getNext() != null) {
                    getNext().handleRequest(LeaveDays);
                } else {
                    System.out.println("请假天数太多,没有人批准该假条!");
                }
            }
        }
    }

22.4、模式的应用场景

前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。

  1. 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
  2. 可动态指定一组对象处理请求,或添加新的处理者。
  3. 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

22.5、模式的扩展

职责链模式存在以下两种情况。

  1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
  2. 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

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

1) SpringMVC-HandlerExecutionChain 类就使用到职责链模式

2) 代码分析+Debug源码+说明

3) 说明

- springmvc 请求的流程图中,执行了 拦截器相关方法 interceptor.preHandler 等等

- 在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式

- HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程

- HandlerExecutionChain 维护了 HandlerInterceptor 的集合, 可以向其中注册相应的拦截器.

22.7、职责链模式的注意事项和细节

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

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

3) 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值