设计模式(下)

目录

4.行为型模式

1.模板方法模式

2.策略模式

3.命令模式

4.责任链模式

5.状态模式

6.观察者模式

7.中介者模式

8.迭代器模式

9.访问者模式

扩展

10.备忘录模式

11.解释器模式

5.设计模式结构图


4.行为型模式

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式中除了模板方法模式和解释器模式以外都是对象行为模式

1.模板方法模式

概述:定义一个算法结构,而将一些步骤延迟到子类实现。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

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

结构:

  • 抽象类(Abstract Class):负责给出一一个算法的轮廓和骨架。它由一个模板方法和若千个基本方法构成。
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种::
      • 抽象方法 (Abstract Method):一个抽象方法由抽象类声明、 由其具体子类实现。
      • 具体方法(Concrete Method):一个具体方法由-一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
      • 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法, 这类方法名一般为i sXxx,返回值类型为boolean类型。
  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

优点:

  • 提高代码复用性。将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反向控制。通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制, 并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

使用场景:1、算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。2、需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

案例:炒菜

炒菜的步骤是固定的T分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

//抽象类:模板方法和基本方法
public abstract class AbstractClass {
    //模板方法
    public final void cookProcess(){
        pourOil();
        heatOil();
        pourVegetable();
        pourSauce();
        fry();
    }

    //倒油:具体方法
    public void pourOil(){
        System.out.println("倒油");
    }
    //热油:具体方法
    public void heatOil(){
        System.out.println("热油");
    }
    //倒蔬菜是不一样的,(一个下包菜,一个下菜心):抽象方法
    public abstract void pourVegetable();

    //倒调味料:抽象方法
    public abstract void pourSauce();

    //翻炒:具体方法
    public void fry(){
        System.out.println("翻炒,炒熟");
    }
}

//炒菜心类
public class ConcreteClass_Caixin extends AbstractClass{
    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是蒜蓉");
    }
}

//炒包菜类
public class ConcreteClass_Baocai extends AbstractClass{
    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是辣椒");
    }
}

public class Client {
    public static void main(String[] args) {
        //炒包菜
        ConcreteClass_Baocai baocai=new ConcreteClass_Baocai();
        //调用父类的炒菜功能
        baocai.cookProcess();
        System.out.println("==========");
        ConcreteClass_Caixin caixin = new ConcreteClass_Caixin();
        caixin.cookProcess();
    }
}

2.策略模式

概述:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

结构:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

优点:

  • 策略类之间可以自由切换。由于策略类都实现同一一个接口,所以使它们之间可以自由切换。
  • 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则"
  • 避免使用多重条件选择语句(if Telse),充分体现面向对象设计思想。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

使用场景:

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

主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

案例:促销活动

设计模式(下)_第1张图片

//抽象策略类
public interface Strategy {
    void show();
}
//具体策略类,封装算法
public class StrategyA implements Strategy{

    @Override
    public void show() {
        System.out.println("买一送一");
    }
}
//具体策略类,封装算法
public class StrategyB implements Strategy{

    @Override
    public void show() {
        System.out.println("满200减50");
    }
}
//具体策略类,封装算法
public class StrategyC implements Strategy{

    @Override
    public void show() {
        System.out.println("满1000加1元换购一件200元以下商品");
    }
}
//销售员:环境类
public class SalesMan {
    //聚合策略类对象
    private Strategy strategy;

    public SalesMan(Strategy strategy) {
        this.strategy = strategy;
    }
    //由促销员展示促销活动给用户
    public void salesManShow(){
        strategy.show();
    }

    public Strategy getStrategy() {
        return strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
}
public class Client {
    public static void main(String[] args) {
        //春节来了。使用春节促销活动
        SalesMan salesMan=new SalesMan(new StrategyA());
        //展示促销活动
        salesMan.salesManShow();
        System.out.println("===========");
        //中秋节来了。使用中秋节促销活动
        salesMan.setStrategy(new StrategyB());
        //展示促销活动
        salesMan.salesManShow();
        System.out.println("===========");
        //圣诞节来了。使用圣诞节促销活动
        salesMan.setStrategy(new StrategyC());
        //展示促销活动
        salesMan.salesManShow();
    }
}

3.命令模式

请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

概念:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

结构:

  • 抽象命令类(Comnand)角色:定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令, 即宏命令。
  • 方便实现Undo和Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。

使用场景:

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

案例:点餐案例

设计模式(下)_第2张图片

//订单类
public class Order {
    //餐桌号码
    private int diningTable;
    //下单的餐具及份数
    private Map foodDir=new HashMap<>();

    public int getDiningTable() {
        return diningTable;
    }

    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }

    public Map getFoodDir() {
        return foodDir;
    }

    public void setFood(String name,int num) {
        foodDir.put(name,num);
    }
}

//厨师类:接收者
public class SeniorChef {
    public void makeFood(String name,int num){
        System.out.println(num+"份"+name);
    }
}

//抽象命令类
public interface Command {
    void execute();
}

//具体命令类
public class OrderCommand implements Command{
    //持有接收者对象
    private SeniorChef receiver;
    //持有订单对象
    private Order order;

    //命令哪个厨师完成哪个订单
    public OrderCommand(SeniorChef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }

    @Override
    public void execute() {
        System.out.println(order.getDiningTable()+"桌的订单:");
        Map foodDir = order.getFoodDir();
        //遍历map集合
        Set keys = foodDir.keySet();
        for (String foodName : keys) {
            receiver.makeFood(foodName,foodDir.get(foodName));
        }
        System.out.println(order.getDiningTable()+"的饭准备完毕");
    }
}

//服务员类:请求者类
public class Waitor {
    //持有多个命令对象
    private List commands=new ArrayList<>();

    public void setCommand(Command cmd) {
        //将cmd对象存储到list集合中
        commands.add(cmd);
    }
    //发起命令的功能:喊一句订单来了
    public void orderUp(){
        System.out.println("美女服务员:大厨,新订单来了");
        for (Command command : commands) {
            if (command!=null){
                command.execute();
            }
        }
    }
}

public class Client {
    public static void main(String[] args) {
        //创建一个订单对象
        Order order = new Order();
        order.setDiningTable(1);
        order.setFood("西红柿鸡蛋面",1);
        order.setFood("小杯可乐",2);
        //创建一个订单对象
        Order order2 = new Order();
        order2.setDiningTable(2);
        order2.setFood("尖椒肉丝面",1);
        order2.setFood("小杯雪碧",1);
        //创建厨师对象
        SeniorChef receiver=new SeniorChef();
        //创建命令对象
        OrderCommand cmd1=new OrderCommand(receiver,order);
        OrderCommand cmd2=new OrderCommand(receiver,order2);

        //创建调用者:服务员对象
        Waitor invoke=new Waitor();
        invoke.setCommand(cmd1);
        invoke.setCommand(cmd2);
        //让我们服务元发起命令
        invoke.orderUp();
    }
}

4.责任链模式

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花"游戏

概念:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

结构:

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

优点:

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

缺点:

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

使用场景:

  • 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
  • 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 3、可动态指定一组对象处理请求。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

案例:请假

现需要开发一个请假流程控制系统。 请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部i门经理同意;请求3天到7天还需要总经理同意才行。

设计模式(下)_第3张图片

//请假条
public class LeaveRequest {
    //姓名
    private String name;
    //请假天数
    private int num;
    //请假原因
    private String content;

    public LeaveRequest(String name, int num, String content) {
        this.name = name;
        this.num = num;
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public int getNum() {
        return num;
    }

    public String getContent() {
        return content;
    }
}

//抽象处理者类
public abstract class Handler {
    protected final static int NUM_ONE = 1;
    protected final static int NUM_THREE = 3;
    protected final static int NUM_SEVEN = 7;
    //该领导处理的请求天数区间
    private int numStart;
    private int numEnd;
    //声明上级领导
    private Handler nextHandler;

    public Handler(int numStart) {
        this.numStart = numStart;
    }

    public Handler(int numStart, int numEnd) {
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    //设置上级领导对象
    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    //各级领导处理请假条的方法
    protected abstract void handleLeave(LeaveRequest leaveRequest);

    //提交请假条
    public final void submit(LeaveRequest leaveRequest) {
        //该领导进行审批
        this.handleLeave(leaveRequest);
        if (this.nextHandler != null && leaveRequest.getNum() > this.numEnd) {
            //提交给上级领导审批
            this.nextHandler.submit(leaveRequest);
        } else {
            System.out.println("审批流程结束");
        }
    }

}

//小组长类:具体处理着
public class GroupLeader extends Handler{
    public GroupLeader(){
        super(0,Handler.NUM_ONE);
    }
    @Override
    protected void handleLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getNum()+"天"+leaveRequest.getContent());
        System.out.println("小组长审批:同意");
    }

}


//部门经理类:具体处理着
public class Manager extends Handler{
    public Manager(){
        super(Handler.NUM_ONE,Handler.NUM_THREE);
    }
    @Override
    protected void handleLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getNum()+"天"+leaveRequest.getContent());
        System.out.println("部门经理审批:同意");
    }
}

//总经理类:具体处理着
public class GeneralManager extends Handler{
    public GeneralManager(){
        super(Handler.NUM_THREE,Handler.NUM_SEVEN);
    }
    @Override
    protected void handleLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getNum()+"天"+leaveRequest.getContent());
        System.out.println("总经理审批:同意");
    }
}

public class Client {
    public static void main(String[] args) {
        //创建一个请假条对象
        LeaveRequest leaveRequest = new LeaveRequest("小明", 7, "身体不适");
        //创建各级领导对象
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();

        //设置处理者链
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);

        //小明提交请假申请
        groupLeader.submit(leaveRequest);
    }
}

5.状态模式

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

结构:

  • 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。

优点:

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

缺点:

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好。

使用场景:

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。条件、分支语句的代替者。

主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

案例:电梯

原案例:

[例]通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理.例如,如果电梯门]现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。

设计模式(下)_第4张图片

//电梯接口
public interface ILift {
    //定义四个电梯的状态的常量
    int OPENING_STATE=1;
    int CLOSING_STATE=2;
    int RUNNING_STATE=3;
    int STOPPING_STATE=4;
    //设置电梯状态的功能
    void setState(int state);
    //电梯操作的功能
    void open();
    void close();
    void run();
    void stop();
}
//电梯类
public class Lift implements ILift {
    //声明一个记录当前电梯的状态
    private int state;

    @Override
    public void setState(int state) {
        this.state = state;
    }

    //执行开门动作
    @Override
    public void open() {
        switch (state) {//表示当前电梯状态
            case OPENING_STATE:
                //什么是都不做
                break;
            case CLOSING_STATE:
                System.out.println("电梯门打开了");
                setState(OPENING_STATE);
                break;
            case RUNNING_STATE:
                break;
            case STOPPING_STATE:
                System.out.println("电梯门打开了");
                setState(OPENING_STATE);
                break;
        }
    }

    //执行关门动作
    @Override
    public void close() {
        switch (state) {//表示当前电梯状态
            case OPENING_STATE:
                System.out.println("电梯门关闭");
                setState(CLOSING_STATE);
                break;
            case CLOSING_STATE:
                break;
            case RUNNING_STATE:
                //运行的时候是关闭的
                break;
            case STOPPING_STATE:
                break;
        }
    }
    //执行运行动作
    @Override
    public void run() {
        switch (state) {//表示当前电梯状态
            case OPENING_STATE:
                //门开启的时候不能运行
                break;
            case CLOSING_STATE:
                System.out.println("电梯运行");
                setState(RUNNING_STATE);
                break;
            case RUNNING_STATE:
                break;
            case STOPPING_STATE://停止
                System.out.println("电梯运行");
                setState(RUNNING_STATE);
                break;
        }
    }
    //执行停止动作
    @Override
    public void stop() {
        switch (state) {//表示当前电梯状态
            case OPENING_STATE:
                //门开启的时候是停止的
                break;
            case CLOSING_STATE:

                break;
            case RUNNING_STATE:
                System.out.println("电梯停止");
                setState(STOPPING_STATE);
                break;
            case STOPPING_STATE:
                break;
        }
    }
}
public class Client {
    public static void main(String[] args) {
        //创建电梯对象
        Lift lift = new Lift();
        //设置当前电梯状态
        lift.setState(ILift.CLOSING_STATE);
        //打开
        lift.open();

        //关闭
        lift.close();
        //运行
        lift.run();
        //停止
        lift.stop();
    }
}

问题分析:

  • 使用了大量的switch..case这样的判断(if..eIse也是一样),使程序的可阅读性变差。
  • 扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑

状态模式案例:

设计模式(下)_第5张图片

//抽象状态类
public abstract class LiftState {
    //声明环境角色类变量
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }
    //电梯开启操作
    public abstract void open();
    //电梯关闭操作
    public abstract void close();
    //电梯运行操作
    public abstract void run();
    //电梯停止操作
    public abstract void stop();
}

//环境角色
public class Context {
    //定义对应状态对象的常量
    public final static OpeningState OPENING_STATE=new OpeningState();
    public final static ClosingState CLOSING_STATE=new ClosingState();
    public final static RuningState RUNING_STATE=new RuningState();
    public final static StopingState STOPING_STATE=new StopingState();

    //定义一个当前电梯状态变量
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }
    //设置当前状态对象
    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //设置当前状态中的Context
        this.liftState.setContext(this);
    }
    public void open(){
        this.liftState.open();
    }
    public void close(){
        this.liftState.close();
    }
    public void run(){
        this.liftState.run();
    }
    public void stop(){
        this.liftState.stop();
    }
}
//电梯开启状态
public class OpeningState extends LiftState {
    //当前状态要执行的方法
    @Override
    public void open() {
        System.out.println("电梯门开启");
    }

    @Override
    public void close() {
        //修改状态
        super.context.setLiftState(Context.CLOSING_STATE);
        //调用当前状态中的context照片呢个的close方法
        super.context.close();
    }

    @Override
    public void run() {
        //电梯门开启不能run
    }

    @Override
    public void stop() {
        //开门的时候本身就是停止的
    }
}
//电梯关闭状态
public class ClosingState extends LiftState{

    @Override
    public void open() {
        super.context.setLiftState(Context.OPENING_STATE);
        super.context.open();
    }

    @Override
    public void close() {
        System.out.println("电梯门关闭");
    }

    @Override
    public void run() {
        super.context.setLiftState(Context.RUNING_STATE);
        super.context.run();
    }

    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPING_STATE);
        super.context.getLiftState().stop();
    }
}
//电梯运行状态
public class RuningState extends LiftState{

    @Override
    public void open() {
        //运行的时候不可以开电梯门
    }

    @Override
    public void close() {
        //运行的时候电梯门肯定已经关闭了
    }

    @Override
    public void run() {
        System.out.println("电梯运行");
    }

    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPING_STATE);
        super.context.stop();
    }
}
//电梯停止状态
public class StopingState extends LiftState{

    @Override
    public void open() {
        super.context.setLiftState(Context.OPENING_STATE);
        super.context.getLiftState().open();
    }

    @Override
    public void close() {
        super.context.setLiftState(Context.CLOSING_STATE);
        super.context.getLiftState().close();
    }

    @Override
    public void run() {
        super.context.setLiftState(Context.RUNING_STATE);
        super.context.getLiftState().run();
    }

    @Override
    public void stop() {
        System.out.println("电梯停止");
    }
}
public class Client {
    public static void main(String[] args) {
        //创建环境角色对象
        Context context=new Context();
        //设置当前电梯状态
        context.setLiftState(new ClosingState());
        context.open();
        context.run();
        context.close();

        context.stop();
    }
}

6.观察者模式

概念:又被称为发布-订阅(Publish/Subscribe) 模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。 这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

结构:

  • subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一 个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口, 可以增加和删除观察者对象。
  • Concretesubiect:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • Concrereobserver:具体观察者, 实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息[可以实现广播机制]

缺点:

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

使用场景:

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

案例:微信公众号

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

设计模式(下)_第6张图片

//抽象主题角色类
public interface Subject {
    //添加订阅者:观察者对象
    void attach(Observer observer);
    //删除订阅者:
    void detach(Observer observer);
    //通知订阅者,更新消息
    void notify(String message);
}
//抽象观察者类
public interface Observer {
    void update(String message);
}
//具体主题角色类
public class SubscriptionSubject implements Subject {
    //定义一个集合,用来存储多个观察者对象
    private List weiXinUserList = new ArrayList<>();

    @Override
    public void attach(Observer observer) {
        weiXinUserList.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weiXinUserList.remove(observer);
    }

    @Override
    public void notify(String message) {
        //遍历集合
        for (Observer observer : weiXinUserList) {
            //调用观察者对象中的update方法
            observer.update(message);
        }
    }
}
//具体的观察者角色类
public class WeiXinUser implements Observer{
    private String name;

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

    @Override
    public void update(String message) {
        System.out.println(name+"-"+message);
    }
}
public class Client {
    public static void main(String[] args) {
        //创建公众号对象
        SubscriptionSubject subscriptionSubject = new SubscriptionSubject();
        //订阅公众号
        subscriptionSubject.attach(new WeiXinUser("孙悟空"));
        subscriptionSubject.attach(new WeiXinUser("猪悟能"));
        subscriptionSubject.attach(new WeiXinUser("沙悟净"));
        //公众号更新,发出消息给订阅者:观察者
        subscriptionSubject.notify("雙馬程序猿的专栏更新了");
    }
}

7.中介者模式

一般来说, 同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专]的类来管理那些不属于自己的行为。

设计模式(下)_第7张图片

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

结构:

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

优点:

  • 松散耦合。中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身” 了。
  • 集中控制交互。多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。
  • 一对多关联转变为一对一的关联。没有使用中介者模式的时候,同事对象之间的关系通常是一对多的, 引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

缺点:

当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

使用场景:

系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

案例:

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

设计模式(下)_第8张图片

//抽象中介者类
public abstract class Mediator {
    public abstract void constact(String message,Person person);
}
//抽象同事类
public abstract class Person {
    protected String name;
    protected Mediator mediator;//中介

    public Person(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
}
//具体同事角色类
public class Tenant extends Person{
    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }

    //和中介联系(沟通)
    public void constact(String message){
        mediator.constact(message,this);
    }
    //获取信息的方法
    public void getMessage(String message){
        System.out.println("租房者"+name+"获取到的信息是:"+message);
    }
}
//具体同事角色类
public class HouseOwner extends Person{
    public HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }
    //和中介联系(沟通)
    public void constact(String message){
        mediator.constact(message,this);
    }
    //获取信息的方法
    public void getMessage(String message){
        System.out.println("房主"+name+"获取到的信息是:"+message);
    }

}
//具体的中介者角色类
public class MediatorStructure extends Mediator{
    //聚合房主和具体租房者
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    public Tenant getTenant() {
        return tenant;
    }

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    @Override
    public void constact(String message, Person person) {
        if (person==houseOwner){
            tenant.getMessage(message);
        }else {
            houseOwner.getMessage(message);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        //创建中介者对象
        MediatorStructure mediatorStructure=new MediatorStructure();
        //创建租房者对象
        Tenant tenant = new Tenant("李四", mediatorStructure);
        //创建房主对象
        HouseOwner houseOwner = new HouseOwner("张三", mediatorStructure);
        //中介者要知道具体的房主和租房者
        mediatorStructure.setTenant(tenant);
        mediatorStructure.setHouseOwner(houseOwner);

        tenant.constact("我要租三室的房子");
        houseOwner.constact("我这里有三室的房子你要租吗?");
    }
}

8.迭代器模式

概念:供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

结构:

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

优点:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求。

缺点:

  • 增加了类的个数,这在一定程度上增加了系统的复杂性。

使用场景:

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

主要解决:不同的方式来遍历整个整合对象。

案例:定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:

设计模式(下)_第9张图片

public class Student {
    private String name;
    private String number;

//有参无参、getset、toSting
}
//抽象迭代器角色接口
public interface StudentIterator {
    //判断是否还有元素
    boolean hasNext();
    //获取下一个元素
    Student next();
}
//具体迭代器角色类
public class StudentIteratorImpl implements StudentIterator{
    private List list;
    private int position=0;//用来记录遍历时的位置

    public StudentIteratorImpl(List list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return position list=new ArrayList<>();

    @Override
    public void addStudent(Student student) {
        list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
        list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}
public class Client {
    public static void main(String[] args) {
        //创建聚合对象
        StudentAggregateImpl aggregate= new StudentAggregateImpl();
        //添加元素
        aggregate.addStudent(new Student("张三","003"));
        aggregate.addStudent(new Student("李四","004"));
        aggregate.addStudent(new Student("王五","005"));
        aggregate.addStudent(new Student("赵六","006"));
        //遍历聚合对象

        //获取迭代器对象
        StudentIterator iterator=aggregate.getStudentIterator();
        //遍历
        while (iterator.hasNext()){
            //获取元素
            Student student = iterator.next();
            System.out.println(student.toString());
        }
    }
}

9.访问者模式

概念:主要将数据结构与数据操作分离,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

结构:

  • 抽象访问者(Visitor)角色:定义了对每一个元素(ETement) 访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(Concretevisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept) ,意义是指,每- -个元素都要可以被访问者访问。
  • 具体元素(ConcreteRlement)角色:提供接受访问方法的具体实现, 而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为-一个具有容器性质或者复合对象特性的类,它会含有一组元素( ETement) ,并且可以迭代这些元素,供访问者访问。

优点:

  • 扩展性好。在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好。通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  • 分离无关行为。通过访问者来分离无关的行为,把相关的行为封装在一起, 构成一个访问者,这样每一个访问者的功能都比较单一。符合单一职责原则

缺点:

  • 对象结构变化很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
  • 具体元素对访问者公布细节,违反了迪米特原则

使用场景:

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

主要解决:稳定的数据结构和易变的操作耦合问题。

案例:给动物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

设计模式(下)_第10张图片

//抽象访问者角色:给宠物喂食的人
public interface Person {
    //喂食的对象
    void feed(Cat cat);
    void feed(Dog dog);
}
//抽象元素角色:动物抽象类
public interface Animal {
    //接受访问者访问的功能
    void accept(Person person);
}
//具体元素角色:宠物猫
public class Cat implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);//访问者给宠物猫喂食
        System.out.println("猫猫吃食");
    }
}
//具体元素角色:宠物狗
public class Dog implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);//访问者给宠物狗喂食
        System.out.println("狗狗吃食");
    }
}
//具体访问者角色:主人
public class Owner implements Person{
    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}
//具体访问者角色:路人
public class Someone implements Person{
    @Override
    public void feed(Cat cat) {
        System.out.println("路人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("路人喂食狗");
    }
}
//结构对象角色:主人家
public class Home {
    //声明一个集合对象,用来存储元素对象
    private List nodeList=new ArrayList<>();
    //添加元素功能
    public void add(Animal animal){
        nodeList.add(animal);
    }
    //
    public void action(Person person){
        //遍历集合获取每一个元素
        for (Animal animal : nodeList) {
            animal.accept(person);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        //创建home对象
        Home home = new Home();
        //添加元素到home对象中
        home.add(new Dog());
        home.add(new Cat());
        //创建主人对象
        Owner owner=new Owner();
        //宠物饿了,主人去喂食所有宠物
        home.action(owner);
    }
}

扩展

  • 1.分派

变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如Map map = new HashMap() ,map变量的静态类型是Map ,实际类型是HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch)发生在编译时期, 分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch) 发生在运行时期, 动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

  • 2.动态分派
public class Animal {
    public void execute(){
        System.out.println("Animal");
    }
}
public class Cat extends Animal{
    @Override
    public void execute() {
        System.out.println("cat");
    }
}
public class Dog extends Animal{
    @Override
    public void execute() {
        System.out.println("dog");
    }
}
public class Client {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        dog.execute();
        cat.execute();
    }
}

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

  • 3.静态分派

通过方法重载支持静态分派。

public class Animal {
}
public class Cat extends Animal{
}
public class Dog extends Animal{
}
public class Execute {
    public void execute(Animal a){
        System.out.println("Animal");
    }
    public void execute(Dog d){
        System.out.println("Dog");
    }
    public void execute(Cat c){
        System.out.println("Cat");
    }
}
public class Client {
    public static void main(String[] args) {
        Animal a= new Animal();
        Animal d= new Dog();
        Animal c= new Cat();
        Execute execute = new Execute();
        execute.execute(a);
        execute.execute(d);
        execute.execute(c);
    }
}

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

  • 4.双分派

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver) 的运行时区别,还要根据参数的运行时区别。

public class Animal {
    public void accept(Execute execute){
        execute.execute(this);
    }
}
public class Cat extends Animal{
    public void accept(Execute execute){
        execute.execute(this);
    }
}
public class Dog extends Animal{
    public void accept(Execute execute){
        execute.execute(this);
    }
}
public class Execute {
    public void execute(Animal a){
        System.out.println("Animal");
    }
    public void execute(Dog d){
        System.out.println("Dog");
    }
    public void execute(Cat c){
        System.out.println("Cat");
    }
}
public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();
        Execute execute = new Execute();
        a.accept(execute);
        d.accept(execute);
        c.accept(execute);

    }
}

在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

10.备忘录模式

备忘录模式提供了-种状态恢复的实现机制,使得用户可以方便地回到一一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo) 操作,如Word、 记事本、Photoshop、 IDEA等软件在编辑时按Ctrl+Z组合键时能撤销当前操作,使文档恢复到之前的状态;还有在浏览器中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

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

结构:

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

窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface), 这个窄接口只允许他把备忘录对象传给其他的对象。
宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface), 这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

优点:

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

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

使用场景:

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景, 如word、记事本、Photoshop, idea等软件在编辑时按ctrl+Z 组合键,还有数据库中事务操作。

主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

案例:游戏挑战BOSS

游戏中的某个场景,一游戏角色有生命力、 攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

白箱备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:

设计模式(下)_第11张图片

//备忘录角色类
public class RoleStateMemento {
    private int vit;//生命力
    private int atk;//攻击力
    private int def;//防御力

//getset有参无参
}

public class RoleStateCaretaker {
    //声明RoleStateMemento类型的变量
    private RoleStateMemento roleStateMemento;
//getset
}
//游戏角色类 :发起人角色
public class GameRole {
    private int vit;//生命力
    private int atk;//攻击力
    private int def;//防御力

    //初始化内部状态
    public void initState(){
        this.atk=100;
        this.vit=100;
        this.def=100;
    }

    //战斗
    public void fight(){
        this.atk=0;
        this.vit=0;
        this.def=0;
    }
    //保存角色状态功能
    public RoleStateMemento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }

    //恢复角色状态
    public void recoverState(RoleStateMemento roleStateMemento){
        //将备忘录对象中存储的状态赋值给当前对象的成员
        this.vit=roleStateMemento.getVit();
        this.atk=roleStateMemento.getAtk();
        this.def=roleStateMemento.getDef();
    }

    //展示状态功能
    public void stateDisplay(){
        System.out.println("角色生命力:"+vit);
        System.out.println("角色攻击力:"+atk);
        System.out.println("角色防御力:"+def);
    }
    
//getset
}
public class Client {
    public static void main(String[] args) {
        System.out.println("----------大战boos前----------");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        gameRole.initState();//初始状态操作
        gameRole.stateDisplay();
        //将该游戏角色内部状态进行备份
        //创建管理者对象
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

        System.out.println("----------大战boos后----------");
        //损耗严重
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("----------恢复之前状态----------");
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。

黑箱备忘录 

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将RolestateMemento 设为GameRole 的内部类, 从而将RolestateMemento 对象封装GameRole 里面;在外面提供一个标识接口Memento给RolestateCaretaker 及 其他对象使用。这样GameRole 类看到的是RolestateMemento 所有的接口,而Rolestatecaretaker及其他对象看到的仅仅是标识接口Memento 所暴露出来的接口,从而维护了封装型。类图如下:设计模式(下)_第12张图片

//备忘录接口:对外提供窄接口
public interface Memento {
}
//游戏角色类 :发起人角色
public class GameRole {
    private int vit;//生命力
    private int atk;//攻击力
    private int def;//防御力

    //初始化内部状态
    public void initState(){
        this.atk=100;
        this.vit=100;
        this.def=100;
    }

    //战斗
    public void fight(){
        this.atk=0;
        this.vit=0;
        this.def=0;
    }
    //保存角色状态功能
    public Memento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }

    //恢复角色状态
    public void recoverState(Memento memento){
        RoleStateMemento roleStateMemento= (RoleStateMemento) memento;
        //将备忘录对象中存储的状态赋值给当前对象的成员
        this.vit=roleStateMemento.getVit();
        this.atk=roleStateMemento.getAtk();
        this.def=roleStateMemento.getDef();
    }

    //展示状态功能
    public void stateDisplay(){
        System.out.println("角色生命力:"+vit);
        System.out.println("角色攻击力:"+atk);
        System.out.println("角色防御力:"+def);
    }
//getset
    private class RoleStateMemento implements Memento{
        private int vit;//生命力
        private int atk;//攻击力
        private int def;//防御力

//有参无参、getset
    }
}

public class RoleStateCaretaker {
    //声明RoleStateMemento类型的变量
    private Memento memento;
//getset
}
public class Client {
    public static void main(String[] args) {
        System.out.println("----------大战boos前----------");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        gameRole.initState();//初始状态操作
        gameRole.stateDisplay();
        //将该游戏角色内部状态进行备份
        //创建管理者对象
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setMemento(gameRole.saveState());

        System.out.println("----------大战boos后----------");
        //损耗严重
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("----------恢复之前状态----------");
        gameRole.recoverState(roleStateCaretaker.getMemento());
        gameRole.stateDisplay();
    }
}

11.解释器模式

概念:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

文法(语法规则):

文法是用于描述语言的语法结构的形式规则。

expression ::= value| plus| minus
plus ::= expression + expression
minus ::= expression '-' expression
value ::= integer

注意:这里的符号::=表示“定义为”的意思,竖线 表示或,左右的其中一个,引号内为字符本身,引号外为语法

上面规则描述为:
表达式可以是一个值, 也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在计算机科学中,抽象语法树(AbstractsyntaxTree, AST) , 或简称语法树(syntax tree) , 是源代码语法结构的一种抽象表示。
它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
用树形来表示符合文法规则的句子。

设计模式(下)_第13张图片

结构:

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

优点:

  • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
  • 实现文法较为容易。在抽象语法树中每一个表达式节 点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。
  • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合"开闭原则"。.

缺点:

  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。(类膨胀)
  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。(递归)

使用场景:

  • 当语言的文法较为简单,且执行效率不是关键问题时。
  • 当问题重复出现,且可以用一种简单的语言来进行表达时。
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。

主要解决:对于一些固定文法构建一个解释句子的解释器。

案例:设计加减法的功能

设计模式(下)_第14张图片

//抽象表达式类
public abstract class AbstractExpression {
    public abstract int interpret(Context context);
}

//环境角色类
public class Context {
    //定义一个map集合,用来存储变量及对应的值
    private Map map=new HashMap<>();
    //添加变量的功能
    public void assign(Variable variable,Integer value){
        map.put(variable,value);
    }
    //根据变量获取对应的值
    public int getValue(Variable variable){
        return map.get(variable);
    }
}
//封装变量的类
public class Variable extends AbstractExpression{
    //声明存储变量名的成员变量
    private String name;

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

    @Override
    public int interpret(Context context) {
        //直接返回变量值
        return context.getValue(this);
    }

    @Override
    public String toString() {
        return name;
    }
}
//加法表达式类
public class Plus extends AbstractExpression {
    // +(加号)左边的表达式
    private AbstractExpression left;
    // +(加号)右边的表达式
    private AbstractExpression right;

    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        //将左边表达式的结果和右边表达式的结果进行相加
        return left.interpret(context) + right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + "+" + right.toString() + ")";
    }
}
//减法表达式类
public class Minus extends AbstractExpression {
    // -(减号)左边的表达式
    private AbstractExpression left;
    // -(减号)右边的表达式
    private AbstractExpression right;

    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        //将左边表达式的结果和右边表达式的结果进行相减
        return left.interpret(context) - right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + "-" + right.toString() + ")";
    }
}
public class Client {
    public static void main(String[] args) {
        //创建环境对象
        Context context = new Context();
        //创建变量对象
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");
        //将变量存储到环境对象中
        context.assign(a,1);
        context.assign(b,2);
        context.assign(c,3);
        context.assign(d,4);

        //获取抽象语法树
        AbstractExpression expression=new Minus(a,new Plus(new Minus(b,c),d));
        //解释
        int result = expression.interpret(context);
        System.out.println(expression+"="+result);
    }
}

5.设计模式结构图

设计模式(下)_第15张图片

你可能感兴趣的:(运维,java)