23种设计模式讲解之行为型篇(Java实现并附保姆级注释)

213 阅读20分钟

前言

最近系统复习设计模式,边学习边实现,学习过程中我主要参考了程杰的《大话设计模式》和这个网站。大话中的示例代码是C++,后者网站虽然用的是Java,但每一个示例都用窗体展示,夹杂了大量无关模式的awt和swing代码,不利于集中理解模式运作过程。网上大量关于设计模式的文章总是充斥着很多抽象说明和到处复制粘贴的UML图,对初学者理解模式本身反而造成干扰。例如下面这种抽象描述,实际上是对模式完全理解之后的高度概括,很多文章上来就写这种抽象定义,完全是浪费读者时间。这种描述对模式识别的理解是不是必须的,甚至对初学者是有害的。

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

因此我会用三篇文章,侧重模式的代码实现,去芜存菁地讲解每一个模式,每一个代码示例我都已确保在本地成功运行,且代码尽可能遵守Java编程规范(主要是阿里的规范)。每一个模式讲解顺序如下:

  • 模式说明:简要说明模式使用场景和该模式相比简单粗暴的方法(通常是if-esle或者单个类封装)的好处,另外像组合模式里有透明方式和安全方式,单例模式里有饿汉方式和懒汉方式,模板模式里有钩子方法也会简要说明。
  • 结构:列明该模式用到的抽象和具体类。
  • 代码演示:可执行的符合Java编程规范的模式示例代码,并对关键语句都加上了注释。

这是第三篇,行为型篇,其他两篇文章地址如下:

23种设计模式讲解之创建型篇(Java实现并附保姆级注释)

23种设计模式讲解之结构型篇(Java实现并附保姆级注释)

行为型模式

描述多个对象间如何互相协作完成单个对象难以完成的任务,包括如下:

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 责任链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

模板方法模式

模式说明

有这样一些对象,他们都要执行若干动作,这些动作中多数类似或完全相同,例如在不同客户在到银行处理业务,都需要取号,排队,与柜员交流,处理后对服务评分等。在这里取号,排队,评分动作是完全相同的,与柜员交流的细节可能不同,可能是办理存款业务,也可能是取款,购买基金等。针对此类场景,可以将设置一个抽象模板类,类内声明相关方法,再定义一个驱动方法来调用这些相关方法。这样子类继承抽象模板类后自动复用了大部分的代码,对于具体实现中有区别的细节,在子类中重写即可。

本示例以大学生和小学生入学活动为例,将入学活动中相同的部分放在抽象模板类中,子类继承时自动获得,而子类中不同部分在抽象类中定义为抽象方法或钩子方法,具体实现延迟到子类中。

结构

抽象模板类

  定义方法骨架,并确定一个procedure方法,将需要执行的方法集中放置其中,客户端调用该procedure即可统一执行多个既定的方法。该类内方法分为普通方法,抽象方法和钩子方法。

具体实现类

  继承抽象模板类,按自身情况实现其中的各种方法。

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 模板方法模式
 */
public class TemplateMethodDemo {

    public static void main(String[] args) {
        Admission ad1 = new CollegeAdmission();
        System.out.println("====大学生的入学活动====");
        ad1.templateProcedure();
        System.out.println("====小学生的入学活动====");
        Admission ad2 = new PrimarySchoolAdmission();
        ad2.templateProcedure();
    }

}

/**
 * 抽象模板类
 * 定义方法骨架,并确定一个procedure方法,将需要执行的方法集中放置,客户端
 * 调用该procedure即可统一执行多个既定的方法。该类内方法分为普通方法,抽象
 * 方法和钩子方法。普通方法是所有子类均的共同步骤,且细节一致。抽象方法也是
 * 共同步骤,但细节不同,延迟到子类中实现。钩子方法分为简单钩子和挂载钩子方法。
 * 对于简单钩子方法,抽象类中将其声明为空方法体的普通方法,子类可以选择重写
 * 扩展,若不重写则执行空方法体(相当于不执行)。对于挂载钩子方法,抽象类中先
 * 定义一个用于判断是否执行挂载钩子方法的返回boolean类型的抽象方法。再在挂载
 * 钩子方法中通过一个if-else判断上述boolean类型方法的返回值,true则执行,
 * false则进入空分支,相当于不执行。boolean类型抽象方法延迟到子类实现,由
 * 子类决定是否进入执行挂载钩子方法的执行分支。挂载钩子方法本身在抽象类中可以
 * 作为普通方法,这样子类中就不必重写了。
 */
abstract class Admission{
    public void templateProcedure() {
        healthCheck();
        payTuition();
        militaryTraining();
        morningExercise();
        registerClasses();
    }
    public void healthCheck() {
        System.out.println("到教育部指定体检机构体检。");
    }
    public void payTuition() {
        System.out.println("通过教育部统一学费缴纳平台缴费。");
    }
    // 将军训设置为一个简单钩子方法,具体实现类可选择实现
    public void militaryTraining() {};
    // 将早操设置为一个挂载钩子方法,具体实现类中先实现hasMorningExcercise,
    // true则执行(要做早操),false不执行(进入空分支,相当于不执行)
    public abstract boolean hasMorningExcercise();
    public void morningExercise() {
        if(hasMorningExcercise()) {
            System.out.println("要做早操。");
        } else {}
    }
    public abstract void registerClasses();
    public abstract void choseDormitory();
}

/**
 * 具体实现类
 * 继承抽象模板类,按自身情况实现其中的各种方法。
 * 下例是大学生入学活动类。
 */
class CollegeAdmission extends Admission {
    // 具体实现类中扩展钩子方法
    @Override
    public void militaryTraining() {
        System.out.println("高校新生入学后要参加军训。");
    }
    // 重写抽象类中的挂载钩子方法的触发方法
    @Override
    public boolean hasMorningExcercise() {
        return false;
    }
    @Override
    public void registerClasses() {
        System.out.println("高校新生课程注册由新生自行完成。");
    }

    @Override
    public void choseDormitory() {
        System.out.println("高校新生入学后请在校内宿舍系统内选择宿舍。");
    }
}

/**
 * 具体实现类
 * 下例是小学生入学活动类。
 */
class PrimarySchoolAdmission extends Admission{
    // 没有实现抽象类中的简单钩子方法militaryTraining
    // 重写抽象类中的挂载钩子方法的触发方法
    @Override
    public boolean hasMorningExcercise() {
        return true;
    }
    @Override
    public void registerClasses() {
        System.out.println("小学新生课程注册由班主任完成。");
    }
    
    @Override
    public void choseDormitory() {
        System.out.println("小学新生原则上不提供校内住宿,特殊情况请单独申请。");
    }
}

策略模式

模式说明

处理同样的事情可以有多种方法,例如排序数组,可以用冒泡,选择,插入,希尔,快速等等排序方法,对于同样的输入有多种策略能来处理并输出不同策略处理的结果。在程序中使用某一种策略,最直接的方式是在在客户端中将不同的策略写入if-elseswitch-case的分支,然后由客户端进行选择。缺点是客户端职责过大,违背单一职责原则,其次是某个策略变动需要修改客户端代码,违背开闭原则。针对此类场景可以使用策略模式。每一种策略单独成类,均继承一个抽象策略类,设置一个持有策略类的环境,客户端不必了解每个策略细节,而是声明某个具体策略后,借由上下文类来执行其中的策略动作。如需增减策略,只需要增减该策略的类。一个已存在的策略出现变动,也只需要修改该策略本身,增删改都不会影响已有程序,满足开闭原则。一个策略类实现一种策略方法,满足单一职责原则。

本示例以处理收款为例,演示客户端如何通过环境类来指定某种收款策略得到应收结果。

结构

抽象策略类

  定义一个收款抽象方法acceptCash

具体策略类

  继承抽象策略类,实现具体策略。

环境类(上下文类)

  持有一个收费策略类,维护一个收款方法,该方法内实际调用收费策略类的收款方法。

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 策略模式
 */
public class StrategyDemo {

    public static void main(String[] args) {
        // 声明收费具体策略类cash1,按85折收费
        Cash cash1 = new CashDiscount(0.15);
        // 声明收费具体策略cash2,正常收费
        Cash cash2 = new CashNormal();
        // 声明收费上下文,通过构造器初始化收费策略为cash1
        CashContext cc1 = new CashContext(cash1);
        // 声明收费上下文,通过构造器初始化收费策略为cash2
        CashContext cc2 = new CashContext(cash2);
        // 付款100,返回cash1策略的应收费用
        double requestMoney1 = cc1.acceptCash(100);
        // 输出“85.0”
        System.out.println(requestMoney1);
        // 付款100,返回cash1策略的应收费用,输出“100”
        double requestMoney2 = cc2.acceptCash(100);
        // 输出“100.0”
        System.out.println(requestMoney2);
    }

}

/**
 * 抽象策略类
 * 定义一个收款抽象方法acceptCash,入参是客户付款,返回应收费用
 */
abstract class Cash{
    public abstract double acceptCash(double money);
}

/**
 * 具体策略类
 * 继承抽象策略类,实现具体策略。
 * 下例是按原价收费。
 */
class CashNormal extends Cash{
    @Override
    public double acceptCash(double money) {
        return money;
    }
}

/**
 * 具体策略类
 * 下例是按折扣收费,折扣在声明具体Cash类时通过构造器传入并初始化。
 */
class CashDiscount extends Cash{
    private double rate;
    
    public CashDiscount(double discount) {
        rate = 1.0 - discount;
    }
    @Override
    public double acceptCash(double money) {
        return money * rate;
    }
}

/**
 * 环境类(上下文类)
 * 持有一个收费策略类,并通过有参构造器传入收费策略实例初始化。
 * 维护一个收款方法,内部调用其所持有的具体收费策略的收款方法。
 */
class CashContext{
    private Cash cash;
    
    public CashContext(Cash cash) {
        this.cash = cash;
    }
    public double acceptCash(double money) {
        return cash.acceptCash(money);
    }
}

命令模式

模式说明

请求某个命令执行者执行命令时,最直接的方式是客户端直接传入命令给执行者,但这样就无法管理命令(或者说只能由客户端来管理),加重了客户端的负担。此类场景可以将命令封装成类,其内持有命令执行者实例,另有一个execute方法,该方法调用命令执行者实例的执行方法。再引入一个命令请求者(或称命令传递者,命令管理者)类来持有命令集合实例,其内有追加命令方法,删除命令方法,最重要的是有一个传递命令的方法request,该方法通过调用其持有的命令对象的execute方法来实现请求(execute内部调用了命令执行者自身的执行方法)。

本示例展示点菜场景下,命令请求者服务员(Waiter类)如何管理命令(Order类),并通过request调用Order内的excute方法,让厨师(Chef类)执行做菜方法。

结构

抽象命令类:

  持有一个命令执行者Chef类,并通过带参构造方法初始化Chef属性。有一个抽象方法execute

具体命令类

  继承抽象命令类,实现execute方法,内部调用Chef的做菜方法。

命令执行类

  执行命令的角色Chef,内有做菜方法(本例中的makeMuttonmakeChickenWings)。

命令请求者类

  管理命令和请求执行者执行命令的角色,类内以List持有多个命令实例。维护追加命令方法addOrder,撤销命令方法cancelOrder和请求方法requestrequest内遍历Order并调用其execute方法。

代码演示

package com.yukiyama.pattern.behavior;

import java.util.ArrayList;
import java.util.List;

/**
 * 命令模式
 */
public class CommandDemo {

    public static void main(String[] args) {
        // 声明命令执行者
        Chef chef = new Chef();
        // 声明具体命令,通过构造器传入该命令对应的执行者(本例只有一个执行者)
        Order muttonOrder = new MuttonOrder(chef);
        Order wingsOrder = new ChickenWingsOrder(chef);
        // 声明命令请求者
        Waiter waiter = new Waiter();
        // 将命令传入请求者内
        waiter.addOrder(muttonOrder);
        waiter.addOrder(wingsOrder);
        // 命令请求者执行request方法,输出"烤羊肉串",“烤鸡翅”
        waiter.request();
    }

}

/**
 * 抽象命令类
 * 持有命令执行者类Chef,通过有参构造器传入Chef来初始化。有一个execute抽象方法。
 */
abstract class Order{
    protected Chef chef;
    
    public Order(Chef chef){
        this.chef = chef;
    }
    public abstract void execute();
}

/**
 * 具体命令类
 * 实现抽象命令类,实现execute方法,方法内部调用其持有的Chef的做菜方法。
 * 下例是点羊肉串的命令MuttonOrder,execute方法内调用Chef的做羊肉串方法
 * makeMutton。
 */
class MuttonOrder extends Order{
    public MuttonOrder(Chef chef) {
        super(chef);
    }
    @Override
    public void execute() {
        chef.makeMutton();
    }
}

/**
 * 具体命令类
 * 下例是点鸡翅的命令ChickenWingsOrder,execute方法内调用Chef的做鸡翅
 * 方法makeChickenWings。
 */
class ChickenWingsOrder extends Order{
    public ChickenWingsOrder(Chef chef) {
        super(chef);
    }
    public void execute() {
        chef.makeChickenWings();
    }
}

/**
 * 命令请求者类
 * 持有一个命令实例集合List<Order>。维护管理命令的方法addOrder,cancel,
 * request。其中传递命令的请求方法request遍历该类持有的命令集合,并执行命令
 * 类的execute方法。
 */
class Waiter{
    private List<Order> orders = new ArrayList<>();
    
    public void addOrder(Order order) {
        orders.add(order);
    }
    public void cancelOrder(Order order) {
        orders.remove(order);
    }
    public void request() {
        for(Order order : orders) {
            order.execute();
        }
    }
}

/**
 * 命令执行者类
 * 真正执行命令的角色,维护做菜方法。
 * 下例是烤羊肉串MakeMutton和烤鸡翅MakeChickenWings方法。
 */
class Chef{
    public void makeMutton() {
        System.out.println("烤羊肉串");
    }
    public void makeChickenWings() {
        System.out.println("烤鸡翅");
    }
}

责任链模式

模式说明

工作中可能有这样的请求处理场景,例如假期审批,会根据申请假期的天数长短,由不同权限的主管者审批,但提交申请时总是提交给自己的直接主管,由直接主管来判断是否需要上升到更高权限的管理者来审批。类似场景可以描述为一个请求根据其内容由不同级别的处理者处理,申请入口总是最低级别的处理者。在客户端中处理此类请求,最简单直接的做法用if-else语句遍询所有处理者,最终总能够被某一权限的处理者处理。这种做法的缺点是客户端责任太大,违背了单一职责原则,另外可扩展性也很差,当需要修改或删减处理者时,需要修改客户端代码。对于这种场景可以使用责任链模式实现,创建每一层级的处理者类,内部均实现一个处理方法,请求从最底层处理者开始,每一层级处理者处理请求时,如判断不在权限范围内,则向后继处理者传递该请求,直至最后一层。这种做法很好地实践了单一职责原则,由于每一层级处理者均为一类,对处理者的变化也有良好的扩展能力。

本示例以假期申请为例,演示不同天数的申请如何通过责任链传递从最低层级管理者传递到具有相应权限的管理者处并得到处理。

结构

抽象处理者类:

  处理请求的对象,类内实现一个设置后继处理者的普通方法,定义一个处理请求的抽象方法。

具体处理者类

  继承抽象处理者类,实现抽象方法。

请求类(可选)

  当请求比较复杂时,可以将其封装成类。当请求比较简单,如申请假期的场景,可以只用int表示请求。

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 责任链模式
 */
public class ResponsibilityChainDemo {

    public static void main(String[] args) {
        // 声明责任链上所有级别的处理者
        // L1处理者有小于3天的请假审批权
        Manager L1 = new L1Manager("一级主管");
        // L2处理者有3到10天的请假审批权
        Manager L2 = new L2Manager("二级主管");
        // L3处理者有20天以内的请假审批权,超过则驳回
        Manager L3 = new L3Manager("三级主管");
        // 从低到高,每个层级的处理者设置自己的上级(后继)处理者
        // 使请求能够从最低级处理者开始传递到最高级处理者
        L1.setSuperior(L2);
        L2.setSuperior(L3);
        
        // 声明一个请求并设置请求的内容
        Application app = new Application();
        app.setDaysNum(2);
        // 每次申请均只需由最低级处理者一级主管L1来执行申请
        // 输出"一级主管批准2天请假申请。"
        L1.apply(app);
        
        app.setDaysNum(5);
        // 输出“二级主管批准5天请假申请。”
        L1.apply(app);
        
        app.setDaysNum(15);
        // 输出“三级主管批准15天请假申请。”
        L1.apply(app);
        
        app.setDaysNum(21);
        // 输出“该请假申请天数为21天,三级主管驳回超过20天假期申请。”
        L1.apply(app);
    }

}

/**
 * 抽象处理者类
 * 持有一个name属性,并在有参构造器中初始化该属性。持有自己的后继处理者
 * superior,通过普通方法setSuperior设置。定义了一个处理申请的抽象
 * 方法apply(Application app)。
 */
abstract class Manager{
    protected String name;
    protected Manager superior;
    
    public Manager(String name) {
        this.name = name;
    }
    public void setSuperior(Manager superior) {
        this.superior = superior;
    }
    public abstract void apply(Application app);
}

/**
 * 具体处理者类
 * 继承抽象处理者类,实现了抽象方法apply,根据申请内容的不同,不能处理时
 * 调用后继处理者的apply方法交由后继处理者处理。
 * 下例是最低级别的处理者L1。
 */
class L1Manager extends Manager{
    public L1Manager(String name) {
        super(name);
    }
    @Override
    public void apply(Application app) {
        if(app.getDaysNum() < 3) {
            System.out.printf("%s批准%d天请假申请。\n", name, app.getDaysNum());
        } else {
            if(superior != null) {
                superior.apply(app);
            }
        }
    }
}

/**
 * 具体处理者类
 * 下例是L1的后继处理者L2。
 */
class L2Manager extends Manager{
    public L2Manager(String name) {
        super(name);
    }
    @Override
    public void apply(Application app) {
        if(app.getDaysNum() >= 3 && app.getDaysNum() <= 10) {
            System.out.printf("%s批准%d天请假申请。\n", name, app.getDaysNum());
        } else {
            if(superior != null) {
                superior.apply(app);
            }
        }
    }
}

/**
 * 具体处理者类
 * 下例是L2的后继处理者L3。
 */
class L3Manager extends Manager{
    public L3Manager(String name) {
        super(name);
    }
    @Override
    public void apply(Application app) {
        if(app.getDaysNum() > 10 && app.getDaysNum() <= 20) {
            System.out.printf("%s批准%d天请假申请。\n", name, app.getDaysNum());
        } else {
            System.out.printf("该请假申请天数为%d天,%s驳回超过20天假期申请。\n", app.getDaysNum(), name);
        }
    }
}

/**
 * 请求类(可选)
 * 当请求包含较多属性时,可以将请求封装成类。当请求比较简单如申请放假天数,
 * 也可以只用基本数据类型。
 */
class Application{
    private int daysNum;

    public int getDaysNum() {
        return daysNum;
    }
    public void setDaysNum(int daysNum) {
        this.daysNum = daysNum;
    }
    
}

状态模式

模式说明

一个对象根据自身属性的变化而做出相应的动作,生活中该场景很常见,例如根据心情的不同,人(心情是人的属性)会做出不同的事情,例如高兴时手舞足蹈,悲伤时哭泣等等。在客户端实现此类场景时,最简单的做法是用if-elseswitch-case做分支判断/选择。缺点是显而易见的,当变量变化的种类增加时,对象需要相应地增加处理分支,又或者某一变化的动作需要修改时,也要改动源码,这将违背开闭原则。客户端责任过多,违背单一职责原则,另外程序也不易扩展。对于此类场景,可以使用状态模式实现。以一个环境类(上下文类)代表前述对象,其内部持有当前状态属性值和当前状态实例。环境类主要维护三个方法,状态属性值设置方法,状态实例设置方法,对应当前状态的响应方法,该响应方法内部调用不同状态类的响应方法。不同状态单独成类,均继承自一个抽象状态类。客户端使用时,声明环境类,设置状态值后执行环境类的响应方法。方法内实际调用由声明环境类时通过构造器初始化的默认状态实例的响应方法。每个状态类的响应方法会判断状态值,符合本状态实例的要求就处理,否则将自身状态修改为下一个状态(新状态实例赋值给状态对象属性)。如此就可以实现状态的自动转移,直到某个能响应的状态或到最后一个状态也未能响应,处理结束。

状态模式与责任链模式比较

与责任链模式的相似点是都有处理传递的动作,不同之处是责任链模式中所有层级的处理者对象共存,从低往高传。状态模式则是一个环境对象从某个状态开始响应状态值,在当前状态不能响应的情况下,自身状态改变为下一状态,不存在多状态共存的情况

本示例以根据分数定等级的场景为例,演示如下内容。客户端为一个分数评级类(环境类)

ScoreLevel设置分数后,执行该评级类的响应方法queryLevel返回该分数对应的等级。

结构

抽象状态类

  持有状态的一些属性,定义一个状态响应方法。

具体状态类

  继承抽象状态类,实现抽象状态类的抽象方法。结合传入的分数评级类(环境类)判断响应或转移状态。

环境类(上下文类)

  持有当前状态值和当前状态实例。维护四个方法,getScore/setScoresetScoreState用于设置当前状态实例,queryLevel响应当前状态,返回评级结果。

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 状态模式
 */
public class StateDemo {

    public static void main(String[] args) {
        // 声明一个环境类
        ScoreLevel scoreLevel = new ScoreLevel();
        // 给出当前状态值
        scoreLevel.setScore(50);
        // 输出“该成绩等级为D。”
        String level1 = scoreLevel.queryLevel();
        System.out.printf("该成绩等级为%s。\n", level1);
        
        scoreLevel.setScore(60);
        String level2 = scoreLevel.queryLevel();
        // 输出“该成绩等级为C。”
        System.out.printf("该成绩等级为%s。\n", level2);
        
        scoreLevel.setScore(80);
        String level3 = scoreLevel.queryLevel();
        // 输出“该成绩等级为B。”
        System.out.printf("该成绩等级为%s。\n", level3);
        
        scoreLevel.setScore(90);
        String level4 = scoreLevel.queryLevel();
        // 输出“该成绩等级为A。”
        System.out.printf("该成绩等级为%s。\n", level4);
    }

}

/**
 * 抽象状态类
 * 持有状态的一些属性,定义一个状态响应方法,参数是环境类实例。
 * 本示例只有一个等级属性。
 */
abstract class ScoreState{
    protected String level;
    public abstract String queryLevel(ScoreLevel sl);
}

/**
 * 具体状态类
 * 实现抽象状态类的抽象方法。通过传入的分数评级类(环境类)获取到当前状态值
 * (分数),判断是否可以响应,可以时返回响应结果,否则调用分数评级类的的
 * setScoreState方法,将当前状态实例设置为C等级状态(状态转移)。
 * 下例是D等级的状态类,为D对应的分数做出响应,令level属性值为D并返回。
 * 判断不能响应时将状态改为C等级状态。
 */
class DScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() < 60) {
            level = "D";
            return level;
        } else {
            sl.setScoreState(new CScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是C等级的状态类,为C对应的分数做出响应,令level属性值为C并返回。
 * 判断不能响应时将状态改为B等级状态。
 */
class CScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() >= 60 && sl.getScore() < 80) {
            level = "C";
            return level;
        } else {
            sl.setScoreState(new BScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是B等级的状态类,为B对应的分数做出响应,令level属性值为B并返回。
 * 判断不能响应时将状态改为A等级状态。
 */
class BScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        if(sl.getScore() >= 80 && sl.getScore() < 90) {
            level = "B";
            return level;
        } else {
            sl.setScoreState(new AScoreState());
            return sl.queryLevel();
        }
    }
}

/**
 * 具体状态类
 * 下例是A等级的状态类,为A对应的分数做出响应,令level属性值为A并返回。
 * 最终状态,无转移。
 */
class AScoreState extends ScoreState{
    @Override
    public String queryLevel(ScoreLevel sl) {
        level = "A";
        return level;
    }
}

/**
 * 环境类(上下文类)
 * 持有当前状态值int score和当前状态实例ScoreState state。通过无参
 * 构造器初始化state为DScoreState。维护四个方法,getScore/setScore
 * 方法是score的getter/setter;setScoreState用于设置当前状态实例,
 * 状态转移时使用;queryLevel是状态响应方法,通过调用当前状态实例的
 * queryLevel方法返回评级结果。
 * 下例是一个分数评级类。
 */
class ScoreLevel{
    private int score;
    private ScoreState state;
    
    // 通过构造器初始化当前状态为DScoreState
    public ScoreLevel(){
        state = new DScoreState();
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        if(score >= 0 && score <= 100) {
            this.score = score;
        } else {
            System.out.println("分数输入有误。");
        }
    }
    public void setScoreState(ScoreState state) {
        this.state = state;
    }
    public String queryLevel() {
        return state.queryLevel(this);
    }
}

观察者模式

模式说明

当一个对象变化时,所有与他有关联关系的对象都将因为这个变化而变化,将前者称为目标对象,后者称为观察者对象。例如以人行道上的红绿灯为目标对象,行人和车辆为观察者对象。当红绿灯从红灯变为绿灯时,行人因为这个变化而从等待转变为过马路,而汽车则从通过转变为等待,这些观察者都对目标的变化做出了反映。此类场景可以用观察者模式实现。

本示例以人民币汇率为目标对象,进口公司和出口公司作为观察者对象。演示目标对象变化时,如何通知所有观察者做出其响应的动作。

结构

抽象目标类

  观察者需要观察的对象,类内实现添加和删除观察者的普通方法,定义一个通知所有观察者的抽象方法。

具体目标类

  继承抽象目标类,实现抽象方法。

抽象观察者类

  持有一个name属性,并有对应的getter/setter。定义了一个响应目标对象变化的抽象方法action,被目标类通知时执行。

具体观察者类

  继承抽象观察者类,实现抽象方法action

代码演示

package com.yukiyama.pattern.behavior;

import java.util.ArrayList;
import java.util.List;

/**
 * 观察者模式
 */
public class ObserverDemo {

    public static void main(String[] args) {
        // 声明一个具体目标对象,人民币汇率
        Rate rate = new RMBRate();
        // 声明两个不同的观察者对象,进口公司和出口公司
        Company watcher1 = new ImportCompany();
        Company watcher2 = new ExportCompany();
        // 将观察者都加入到目标对象中,以便目标能够在自身产生变化时通知所有观察者
        rate.add(watcher1);
        rate.add(watcher2);
        // 当汇率升高3.5%时,进出口公司分别对这个变化做出应对
        rate.change(0.035);
        // 当汇率下降6.8%时,进出口公司分别对这个变化做出应对
        rate.change(-0.068);
    }

}

/**
 * 抽象目标类(被观察事物)
 * 持有一个观察者类实例的集合,实现两个非抽象方法,add方法用来添加观察者
 * 实例,remove方法用来删除观察者实例。另有一个抽象方法change,传入变化。
 */
abstract class Rate{
    protected List<Company> companies = new ArrayList<>();
    
    public void add(Company company) {
        companies.add(company);
    }
    public void remove(Company company) {
        companies.remove(company);
    }
    public abstract void change(double changedRate);
}

/**
 * 具体目标类
 * 继承抽象目标类,实现抽象方法change,在方法内遍历其持有的所有观察者,
 * 传入变化,执行观察者的action方法。
 */
class RMBRate extends Rate{
    @Override
    public void change(double changedRate) {
        System.out.printf("人民币汇率变动%.1f%%\n", changedRate*100);
        for(Company company : this.companies) {
            company.action(changedRate);
        }
    }
}

/**
 * 抽象观察者类
 * 持有一个name属性,并有对应的getter/setter。定义了一个action抽象方法。
 * 下例以公司为抽象观察者类。
 */
abstract class Company{
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public abstract void action(double changedRate);
}

/**
 * 具体观察者类
 * 通过无参构造器初始化name。实现抽象观察者类中的抽象方法action。
 * 下例是进口公司观察者,action内实现当人民币汇率降低时减少相应比例
 * 的进口量,升高时增加相应比例的进口量。
 */
class ImportCompany extends Company{
    public ImportCompany() {
        this.setName("进口公司");
    }
    @Override
    public void action(double changedRate) {
        System.out.printf("%s动作:\n", this.getName());
        if(changedRate < 0) {
            System.out.printf("人民币汇率下降了%.1f%%,减少%.1f%%进口量。\n", 
                    -changedRate*100, -changedRate*100);
        } else {
            System.out.printf("人民币汇率上升了%.1f%%,增加%.1f%%进口量。\n", 
                    changedRate*100, changedRate*100);
        }
    }
}

/**
 * 具体观察者类
 * 下例是出口公司观察者,action内实现当人民币汇率降低时增加相应比例
 * 的进口量,升高时减少相应比例的进口量。
 */
class ExportCompany extends Company{
    public ExportCompany() {
        this.setName("出口公司");
    }
    @Override
    public void action(double changedRate) {
        System.out.printf("%s动作:\n", this.getName());
        if(changedRate < 0) {
            System.out.printf("人民币汇率下降了%.1f%%,增加%.1f%%出口量。\n", 
                    -changedRate*100, -changedRate*100);
        } else {
            System.out.printf("人民币汇率上升了%.1f%%,下降%.1f%%出口量。\n", 
                    changedRate*100, changedRate*100);
        }
    }
}

中介者模式

模式说明

存在许多需要两两交互信息的同类对象,如果互相之间直接交互,将会形成网状结构,每一个对象都需要知道所有其他对象,且若某对象需要修改,那么其他对象可能也要做出修改,造成牵一发动全身的不良后果。针对这种场景,可以设置一个中介者,将对象间的网状两两交互,转变为通过中介者来居中传递信息,能够大大降低同类对象间的耦合,系统也会变得易扩展易维护。例如新增一个需要通信的成员(本设计模式中所谓的同事),只需要创建它并注册到中介者中即可,不影响任何其他成员(同事)。

本示例展示中介者闲鱼平台如何向卖家转发买家的商品需求信息,向买家转发卖家的商品上架信息。当有一个买家发布一条商品需求信息时,所有卖家均能收到。当一个卖家发布一条商品上架信息时,所有买家均能收到。

结构

抽象中介者类

  定义管理同事和传递信息的方法。本示例中为注册同事的方法register(User user),转发信息的方法relay(User user, String message)

具体中介者类

  继承抽象中介者。持有一个已完成注册的同事的集合List,实现抽象中介者类中的抽象方法registerrelay

抽象同事类

  持有一个中介者实例属性,一个名字属性,通过有参构造器初始化名字属性。定义三个抽象方法,设置中介者的方法setMediator,发送消息的方法send,接收消息的方法receive

具体同事类

  继承抽象同事类。实现抽象同事类中的三个抽象方法。

代码演示

package com.yukiyama.pattern.behavior;

import java.util.ArrayList;
import java.util.List;

/**
 * 中介者模式
 */
public class MediatorDemo {

    public static void main(String[] args) {
        // 声明一个具体中介者实例闲鱼平台
        Mediator xianyu = new XianyuMediator();
        // 声明卖家1
        User seller1 = new Seller("卖家1");
        // 声明买家1
        User buyer1 = new Buyer("买家1");
        // 声明卖家2
        User seller2 = new Seller("卖家2");
        // 声明买家2
        User buyer2 = new Buyer("买家2");
        // 将买家和卖家都注册到闲鱼中介者上
        xianyu.register(seller1);
        xianyu.register(buyer1);
        xianyu.register(seller2);
        xianyu.register(buyer2);
        // 当卖家1发布一条商品上架信息时,所有买家都能收到通过闲鱼中介者转发的该消息
        seller1.send("上架了一台小米手机。");
        // 当买家1发布一条商品需求信息时,所有卖家都能收到通过闲鱼中介者转发的该消息
        buyer1.send("想要一个皮卡丘手办。");
    }

}

/**
 * 抽象中介者类
 * 定义管理同事和传递信息的方法。本示例中为注册同事的方法register(User user),
 * 转发信息的方法relay(User user, String message)。
 */
abstract class Mediator{
    public abstract void register(User user);
    public abstract void relay(User user, String message);
}

/**
 * 具体中介者类
 * 继承抽象中介者。持有一个已完成注册的同事的集合List<User>,实现抽象
 * 中介者类中的抽象方法register和relay。relay方法参数为用户User和消息
 * message,判断User属于卖家Seller还是买家Buyer,如果是卖家则该消息是
 * 一则商品上架消息,通知所有买家该消息。如果是买家则该消息是一则商品需求
 * 消息,通知所有卖家该消息。
 */
class XianyuMediator extends Mediator{
    private List<User> users = new ArrayList<>();
    
    //将user添加到中介者持有的User类集合中,添加时执行User的setMediator方法
    @Override
    public void register(User user) {
        if(!users.contains(user)) {
            users.add(user);
            user.setMediator(this);
        }
    }
    @Override
    public void relay(User user, String message) {
        // 打印该用户发出的消息
        System.out.println(user.getName()+message);
        // 判断用户类型,如果是卖家
        if(user instanceof Seller) {
            // 所有买家接收该消息
            for(User u : users) {
                if(u instanceof Buyer) {
                    u.receive(user.getName()+message);
                }
            }
        }
        // 判断用户类型,如果是买家
        if(user instanceof Buyer) {
            // 所有卖家接收该消息
            for(User u : users) {
                if(u instanceof Seller) {
                    u.receive(user.getName()+message);
                }
            }
        }
    }
}

/**
 * 抽象同事类
 * 持有一个中介者实例属性,一个名字属性,通过有参构造器初始化名字属性,
 * 并有一个名字属性的getter。
 * 定义三个抽象方法,设置中介者的方法setMediator,发送消息的方法send,
 * 接收消息的方法receive。
 * 本例的同事类为User类。
 */
abstract class User{
    protected Mediator mediator;
    private String name;
    
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public abstract void setMediator(Mediator mediator);
    public abstract void send(String message);
    public abstract void receive(String message);
}

/**
 * 具体同事类
 * 继承抽象同事类。实现抽象同事类中的三个抽象方法。setMediator方法传入
 * 中介者实例,赋值给该同事类持有的中介者属性。send方法调用中介者的转发
 * 方法relay,传入当前同事实例(this)和message。receive方法接收message
 * 并打印出来。
 * 下例是卖家类。
 */
class Seller extends User{
    public Seller(String name) {
        super(name);
    }
    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }
    @Override
    public void send(String message) {
        this.mediator.relay(this, message);
    }
    @Override
    public void receive(String message) {
        System.out.println(this.getName() + "收到一条商品需求信息:" + message);
    }
}

/**
 * 具体同事类
 * 下例是买家类。
 */
class Buyer extends User{
    public Buyer(String name) {
        super(name);
    }
    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }
    @Override
    public void send(String message) {
        this.mediator.relay(this, message);
    }
    @Override
    public void receive(String message) {
        System.out.println(this.getName() + "收到一条商品发布信息:" + message);
    }
}

迭代器模式

模式说明

对于由多个相同元素聚合而成的对象,需要遍历内部元素时,最直接的做法是在客户端通过一个for循环来执行遍历操作。该做法的缺点是对客户端暴露了聚合对象的内部,且增加了客户端的负担。另一种做法是在聚合对象内封装一个遍历方法,在客户端调用该方法。缺点同样明显,当要改变遍历方式(例如原本是从前往后,改成从后往前),就需要修改聚合类中的遍历方法,违背开闭原则。针对这种场景,结合前两种方式的特点,可以将遍历方法分离出来,但不是在客户端中实现,而是创建一个迭代器类,类中持有聚合对象,并实现迭代方法。这样对客户端不用暴露聚合对象内部,又实现了遍历方法和聚合对象的分离。

本示例展示如何在客户端中通过自定义迭代器遍历自定义聚合类内的元素。客户端声明一个具体聚合类(以抽象聚合接口类型声明),然后初始化该聚合的元素。再通过聚合类的getIterator方法获取具体迭代器类实例(以抽象迭代器类型)。最后调用该Iterator的相关方法完成遍历。

结构

迭代器接口

  定义迭代器角色的方法,获取聚合内第一个元素的方法first,获取下一个元素的方法next,判断是否有下一个元素的方法hasNext

具体迭代器类

  迭代器接口的实现类。持有聚合元素的集合List<Object>,持有当前处理元素的下表index。实现fistnexthasNext三个接口方法。

抽象聚合接口

  定义聚合角色的方法,增加聚合元素的方法add,移除聚合元素的方法remove,获取一个迭代器示例的方法getIterator

具体聚合类

  抽象聚合接口的实现类,以List<Object>持有一个聚合示例。实现抽象聚合接口中的addremovegetIterator方法

代码演示

package com.yukiyama.pattern.behavior;

import java.util.ArrayList;
import java.util.List;

/**
 * 迭代器模式
 */
public class IteratorDemo {

    public static void main(String[] args) {
        // 声明一个聚合实例
        AbstractAggregation agg = new MyAggregation();
        // 通过add方法初始化这个聚合
        agg.add("刘备");
        agg.add("关羽");
        agg.add("张飞");
        // 声明一个迭代器实例
        Iterator iter = agg.getIterator();
        // 取出第一个元素
        System.out.println("第一个元素: " + iter.first().toString());
        // 调用迭代器的hasNext和next方法迭代遍历,用toString将元素转为String输出
        System.out.println("====开始迭代遍历====");
        while(iter.hasNext()) {
            System.out.println(iter.next().toString());
        }
    }

}

/**
 * 迭代器接口
 * 定义迭代器角色的方法,获取聚合内第一个元素的方法first,获取下一个元素
 * 的方法next,判断是否有下一个元素的方法hasNext。
 */
interface Iterator{
    Object first();
    Object next();
    boolean hasNext();
}

/**
 * 具体迭代器实现类
 * 迭代器接口的实现类。持有聚合元素的集合List<Object>,并通过有参构造器
 * 初始化该集合。持有当前处理元素的下表index,初始值为-1,表示初识时未
 * 处理任何元素。实现fist,next和hasNext三个接口方法。
 */
class MyIterator implements Iterator{
    private List<Object> list;
    private int index = -1;
    
    public MyIterator(List<Object> list) {
        this.list = list;
    }
    @Override
    public Object first() {
        return list.get(0);
    }
    @Override
    public Object next() {
        Object obj = null;
        // 判断有下一个元素时,获取下一个元素,当前元素下标index+1
        if(this.hasNext()) {
            obj = list.get(index+1);
            index++;
        }
        return obj;
    }
    @Override
    public boolean hasNext() {
        if(index < list.size() - 1) {
            return true;
        } else {
            return false;
        }
    }
}

/**
 * 抽象聚合类接口
 * 定义三个聚合角色的方法,增加聚合元素的方法add,移除聚合元素的方法remove,
 * 获取一个迭代器实例的方法getIterator。
 */
interface AbstractAggregation{
    void add(Object obj);
    void remove(Object obj);
    public Iterator getIterator();
}

/**
 * 具体聚合实现类
 * 抽象聚合接口的实现类,以List<Object>持有一个聚合示例。实现抽象聚合接口
 * 中的add,remove,getIterator方法。其中getIterator获取一个具体迭代器
 * 类的实例,获取方式是通过有参构造器new一个具体迭代类的实例并返回,构造器
 * 参数是聚合类内部持有的聚合对象List<Object> list。
 */
class MyAggregation implements AbstractAggregation{
    private List<Object> list = new ArrayList<>();
    
    @Override
    public void add(Object obj) {
        list.add(obj);
    }
    @Override
    public void remove(Object obj) {
        list.remove(obj);
    }
    @Override
    public Iterator getIterator() {
        return new MyIterator(list);
    }
}

访问者模式

模式说明

以不同地方的厨师对相同食材的处理为例描述访问者模式。例如对于鸡,广东厨师会按粤菜做法做成白斩鸡,而四川厨师会做成川菜辣子鸡。对于鱼也类似,广东厨师将其做成清蒸鱼而四川厨师会处理成水煮鱼。可以把厨师描述为访问者,食材是被访问对象,不同的访问者访问相同的对象有不同的结果。具有这种特征的行为模式就是访问者模式。

本示例中,厨师为抽象访问者,广东厨师和四川厨师为具体访问者。被访问事物是鸡和鱼,它们都继承一个抽象访问对象类。广东厨师处理(访问)鸡和鱼会做出白斩鸡和清蒸鱼,而四川厨师处理鸡和鱼会做出辣子鸡和水煮鱼。设置一个访问对象结构类,其内持有所有的访问对象,有管理访问对象的方法add(增加访问对象)和remove(移除访问对象)。在客户端声明一个访问对象结构,然后add具体的访问对象。接着声明具体访问者,调用访问对象结构的访问方法accpet(传入具体访问者)。最终得到该访问者访问所有访问对象的结果。

结构

抽象访问者类

  定义对所有访问对象的抽象访问方法。

具体访问者类

  继承抽象访问者类,实现抽象访问方法。

抽象访问对象类

  定义抽象访问方法accept,用于接受一个具体的访问者类的访问。

具体访问对象类

  继承抽象访问对象类,实现抽象方法accept

访问对象结构类

  持有访问对象的集合List<Material>,有若干管理访问对象的方法,有一个传入访问者的访问方法,方法内遍历其持有的访问对象并执行访问动作。

代码演示

package com.yukiyama.pattern.behavior;

import java.util.ArrayList;
import java.util.List;

/**
 * 访问者模式
 */
public class VisitorDemo {

	public static void main(String[] args) {
		// 声明一个访问对象结构实例
		ObjectStructure obj = new ObjectStructure();
		// 增加访问对象至结构中
		obj.add(new ChickenMaterial());
		obj.add(new FishMaterial());
		// 声明访问者
		Cook gdCook = new GuangdongCook();
		Cook scCook = new SichuanCook();
		// 访问对象结构执行其accept方法,接收具体访问者的访问
		// 输出“粤菜白斩鸡”,“粤菜清蒸鱼”
		obj.accept(gdCook);
		// 输出”川菜辣子鸡“,”川菜水煮鱼“
		obj.accept(scCook);
	}

}
/**
 * 抽象访问者
 * 定义对所有访问对象的抽象访问方法,参数是所要访问的对象。
 * 下例抽象访问者为厨师,访问对象是鸡和鱼。
 */
abstract class Cook{
	public abstract void make(ChickenMaterial chicken);
	public abstract void make(FishMaterial fish);
}

/**
 * 具体访问者
 * 继承抽象访问者类,实现抽象方法。
 * 下例是广东厨师访问鸡和鱼的动作,分别处理为粤菜白斩鸡和粤菜清蒸鱼。
 */
class GuangdongCook extends Cook {
	@Override
	public void make(ChickenMaterial chicken) {
		System.out.println("粤菜白斩鸡");
	}
	@Override
	public void make(FishMaterial fish) {
		System.out.println("粤菜清蒸鱼");
	}
}

/**
 * 具体访问者
 * 下例是四川厨师访问鸡和鱼的动作,分别处理为川菜辣子鸡和川菜水煮鱼。
 */
class SichuanCook extends Cook {
	@Override
	public void make(ChickenMaterial chicken) {
		System.out.println("川菜辣子鸡");
	}
	@Override
	public void make(FishMaterial fish) {
		System.out.println("川菜水煮鱼");
	}
}

/**
 * 抽象访问对象
 * 定义抽象访问方法accept(Cook cook),用于接受一个具体的访问者类的访问。
 */
abstract class Material{
	public abstract void accept(Cook cook);
}

/**
 * 具体访问对象
 * 实现抽象类中的抽象方法accept(Cook cook)。传入具体访问者,调用访问者
 * 的行为方法(传入此时的访问对象指针this)。
 */
class ChickenMaterial extends Material{
	@Override
	public void accept(Cook cook) {
		cook.make(this);
	}
}

/**
 * 具体访问对象
 */
class FishMaterial extends Material{
	public void accept(Cook cook) {
		cook.make(this);
	}
}

/**
 * 访问对象结构类
 * 持有访问对象的集合List<Material>,管理访问对象,有增加访问对象方法add,
 * 移除访问对象方法remove,执行访问者对访问对象的访问动作accept方法。该
 * 方法内用for-each遍历该结构类持有的访问对象集合,并对每一个访问对象执行
 * 其accpet方法。
 */
class ObjectStructure{
	private List<Material> materials = new ArrayList<>();
	
	public void add(Material material) {
		if(!materials.contains(material)){
			materials.add(material);
		}
	}
	public void remove(Material material) {
		if(materials.contains(material)) {
			materials.remove(material);
		}
	}
	public void accept(Cook cook) {
		for(Material m : materials) {
			m.accept(cook);
		}
	}
}

备忘录模式

模式说明

以游戏中的存档和恢复存档为例,在游戏过程中需要保存当前游戏状态(包括各种游戏角色属性)作为存档,在之后的某个时刻能够恢复这个存档。文档中的撤销操作也有类似的需求。此类场景可以用备忘录模式(也叫快照模式)实现。该模式涉及三个角色,发起人类,备忘录类和备忘录管理者类。发起人类管理系统当前状态,并有一个保存为备忘的存档方法,一个恢复为指定备忘版本的恢复存档方法。备忘录类只管理该备忘的状态信息,而备忘录管理者只管理备忘录实例。符合单一职责原则。

本示例客户端中展示备忘录使用过程。声明一个发起者,该发起者设置系统状态后存档。然后创建当前系统存档(备忘录),并让一个备忘录管理者持有。接着该发起者修改系统状态,最后调用存档恢复方法恢复到修改前状态。

结构

发起人类

  持有所有状态信息,并有对应的getter/setter方法。维护一个存档方法createMemento,返回记录当前系统状态信息的Memento。另有一个恢复指定存档的方法restoreMomento(Memento m),通过参数执行要恢复的Memento

备忘录类

  管理一个备忘中的系统状态信息。本例中只设置了一个状态信息String state,通过构造器初始化该state,并有该stategetter/setter

备忘录管理者类

  持有一个备忘录实例,并有该备忘录实例的getter/setter

代码演示

package com.yukiyama.pattern.behavior;

/**
 * 备忘录模式
 */
public class MementoDemo {

    public static void main(String[] args) {
        // 声明一个发起者实例
        Originator ori = new Originator();
        // 设置状态为“Alive”
        ori.setState("Alive");
        // 初始时状态为"Alive",输出"当前状态为:Alive"
        ori.display();
        // 声明一个备忘录管理者并让其持有当前memento
        MementoManager manager = new MementoManager();
        manager.setMemento(ori.CreateMemento());
        // 修改ori中的状态为"Dead"
        ori.setState("Dead");
        // 此时ori中的状态为"Dead",输出"当前状态为:Dead"
        ori.display();
        // 通过管理者持有的备忘录恢复存档
        ori.restoreMemento(manager.getMemento());
        // 恢复后状态为存档时的"Alive",输出"当前状态为:Alive"
        ori.display();
    }

}

/**
 * 发起人类
 * 持有所有状态信息,并有对应的getter/setter方法。维护一个存档方法
 * createMemento,返回记录当前系统状态信息的Memento。另有一个恢复指定
 * 存档的方法restoreMomento(Memento m),通过参数执行要恢复的Memento。
 */
class Originator{
    private String state;

    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public Memento CreateMemento() {
        return new Memento(state);
    }
    public void restoreMemento(Memento memento) {
        this.state = memento.getState();
    }
    public void display() {
        System.out.println("当前状态为:" + state);
    }
}

/**
 * 备忘录类
 * 管理一个备忘中的系统状态信息。本例中只设置了一个状态信息String state,
 * 通过构造器初始化该state,并有该state的getter/setter。
 */
class Memento{
    private String state;
    
    public Memento(String state) {
        this.state = state;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    
}

/**
 * 备忘录管理者类
 * 持有一个备忘录实例,并有该备忘录实例的getter/setter。
 */
class MementoManager{
    private Memento memento;

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

解释器模式

模式说明

解释器用来处理这样一类对象,该对象可不断划分为更小的对象,使得对该对象的处理和对再分之后的部分的处理类似,且对于不断再分后得到到的最小单位,有最终处理。例如输入歌曲解析出简谱,每个曲子可以划分为更短的曲子,但解析是类似的,直至单个音符可以直接输出简谱符号(或按定义好的规则转换)。该模式类似组合模式,但解释器模式处理对象的元素通常比组合模式的要多,且组合模式是对象结构模式,而解释器 模式是类行为模式。在使用上,组合模式在客户端以树的结构将所有节点按层次组合为一个整体再处理。而对与解释器模式,在客户端直接输入整体,处理时再循环或迭代解释(处理) 非终结表达式直到将迭代至终结符表达式,且所有终结符表达式都被解释完毕。

非终结符: 可再分为非终结符和终结符的元素,例如“我吃饭”可再分为“我(代词,主语)”,“吃饭(动词,谓语)”,“饭(名词,宾语)”。

终结符: 不可再分的语法元素,例如上述的“我”,“吃”,“饭”。

参考: Terminal and nonterminal symbols

本示例定义一种关于加法运算的规则,只有符号"a","b","A","B"之间的加减法是合法运算。客户端将输入一些表达式,通过解释器来判断(解释)该表达式是否符合语法定义,合法输出true,不合法输出false。客户端声明一个上下文类Context,将要解释的表达式传入Contextinterpret方法中,返回解释结果。Context类内持有一个非终结符表达式实例(以抽象表达式类声明)NonterminalExpression,并通过无参构造器初始化该实例NonterminalExpression的构造器是有参的,参数是终结符表达式实例TerminalExpression

结构

抽象表达式类

定义一个抽象解释方法interpret(String expression)

终结符表达式类

继承抽象表达式类。持有一个Set<String>泛型的终结符集合。实现抽象表达式类的抽象解释方法interpret

非终结符表达式类

继承抽象表达式。持有所有种类的终结符表达式实例(以AbstractExpress类型)。实现抽象表达式类中的抽象解释方法interpret

环境类(上下文类)

持有所有种类的终结符集合(以String数组类型),持有一个非终结符实例(以AbstractExpression类型)。维护一个interpret方法,调用非终结符的interpret方法返回解释结果。

代码演示

package com.yukiyama.pattern.behavior;

import java.util.HashSet;
import java.util.Set;

/**
 * 解释器模式
 */
public class InterpreterDemo {

    public static void main(String[] args) {
        // 声明一个上下文实例
        Context context = new Context();
        // 合法表达式,输出“true”
        System.out.println(context.interpret("a"));
        // 合法表达式,输出“true”
        System.out.println(context.interpret("A+b"));
        // 合法表达式,输出“true”
        System.out.println(context.interpret("A+b-a+a-B"));
        // 非法表达式,输出“false”
        System.out.println(context.interpret("C+a"));
        // 非法表达式,输出“false”
        System.out.println(context.interpret("b+a*B"));
        // 非法表达式,输出“false”
        System.out.println(context.interpret("3"));
    }

}

/**
 * 抽象表达式类
 * 定义一个抽象解释方法interpret(String expression)。
 */
abstract class AbstractExpression{
    public abstract boolean interpret(String expression);
}

/**
 * 终结符表达式类
 * 继承抽象表达式类。持有一个Set<String>泛型的终结符集合。并通过有参构造器
 * 初始化。实现抽象表达式类的抽象解释方法interpret。解释过程很简单,判断该
 * 传入的expression是否包含在其持有的终结符集合里,包含则true,否则false。
 */
class TerminalExpression extends AbstractExpression{
    private Set<String> terminals = new HashSet<>();
    
    public TerminalExpression(String[] terminals) {
        for (int i = 0; i < terminals.length; i++) {
            this.terminals.add(terminals[i]);
        }
    }
    public Set<String> getTerminals() {
        return terminals;
    }
    public void setTerminals(Set<String> terminals) {
        this.terminals = terminals;
    }
    @Override
    public boolean interpret(String expression) {
        return terminals.contains(expression);
    }
}

/**
 * 非终结表达式类
 * 继承抽象表达式。持有所有终结符(以AbstractExpress类型),并通过有参构造器
 * 传入终结符集合来初始化。本示例定义两种终结符,小写字母"a"和"b"是一种,大写
 * 字母"A"和"B是另一种。所以本例的非终结符表达式持有两个终结符表达式lowerExp
 * 和upperExp,通过构造器传入lowerExp和upperExp来初始化。
 * 实现抽象表达式类中的抽象解释方法interpret。借助已知的合法的运算符号("+","-")
 * 通过字符串的split方法将原始表达式分割成一个终结符和一个非终结符,对终结符调用
 * 终结符表达式类的interpret判断,一旦非法立即返回false。对非终结符,递归调用
 * 非终结符表达式的interpret。
 */
class NonterminalExpression extends AbstractExpression{
    private AbstractExpression lowerExp;
    private AbstractExpression upperExp;
    
    public NonterminalExpression(AbstractExpression lowerExp, AbstractExpression upperExp) {
        this.lowerExp = lowerExp;
        this.upperExp = upperExp;
    }
    @Override
    public boolean interpret(String exp) {
        boolean isLegal;
        // exp多于1个字符时
        if(exp.length() > 1) {
            // 将表达式分割为两部分,elements[0]为第一个合法运算符号左边的1个字符
            // elements[1]为第一个合法运算符号右边的部分
            String[] elements = exp.split("\\+|\\-", 2);
            // 判断(解释)elements[0]是否合法
            isLegal = lowerExp.interpret(elements[0]) || upperExp.interpret(elements[0]);
            // lements[0]不合法直接返回false
            if(!isLegal){
                return isLegal;
            }
            // 递归调用当前非终结表达式实例的interpret方法判断右边部分
            return this.interpret(elements[1]);
        } else {
            // 若表达式exp等于或少于一个字符时,调用终结符的interpret返回解释结果
            return lowerExp.interpret(exp) || upperExp.interpret(exp);
        }
    }
}

/**
 * 环境类(上下文类)
 * 持有所有种类的终结符集合(以String数组类型),持有一个非终结符实例
 * (以AbstractExpression类型)。无参构造器中初始化非终结符实例,方法如下:
 * 先通过两个String数组实例化两种终结符,再向非终结符构造参数中传入这两个
 * 终结符实例。
 * 维护一个interpret方法,调用非终结符的interpret方法返回解释结果。
 */
class Context{
    private String[] lowers = {"a", "b"};
    private String[] uppers = {"A", "B"};
    private AbstractExpression exp;
    
    public Context() {
        AbstractExpression lowerExp = new TerminalExpression(lowers);
        AbstractExpression upperExp = new TerminalExpression(uppers);
        exp = new NonterminalExpression(lowerExp, upperExp);
    }
    public boolean interpret(String expression) {
        return this.exp.interpret(expression);
    }
}

<-本篇结束->

这是第三篇,行为型篇,其他两篇文章地址如下:

23种设计模式讲解之创建型篇(Java实现并附保姆级注释)

23种设计模式讲解之结构型篇(Java实现并附保姆级注释)