狐言不胡言

导航

曾侯乙编钟引发的遐想之Java设计模式:状态模式

示例

一个类对外提供了多个行为,同时该类对象有多种状态,不同状态下对外的
行为的表现不同,我们该如何来设计该类让它对状态可以灵活扩展?
以自动售卖饮料机为例开发一个程序:

  1. 用户可以在饮料机上进行支付、退款、购买、取货操作
  2. 不同的状态下,这四种操作会有不同的表现
    例如:在用户没有支付的情况下,用户操作了退款、购买、取货会发生什么

简单例子

public class DrinksSellingMachine {
    
    //未支付
    final static int NO_PAY = 0;
    //已支付
    final static int PAY = 1;
    //待取货
    final static int SOLD = 2;
    //已售罄
    final static int SOLD_OUT = 4;

    //当前状态
    private int state = SOLD_OUT;
    //当前库存
    private int store;

    public DrinksSellingMachine(int store) {
        this.store = store;
        if(this.store > 0) {
            this.state = NO_PAY;
        }
    }

    /** 支付 */
    public void pay() {
        switch (this.state) {
            case NO_PAY :
                System.out.println("支付成功,请确定购买饮料。");
                this.state = PAY;
                break;
            case PAY :
                System.out.println("已支付成功,请确定购买饮料。");
                break;
            case SOLD :
                System.out.println("待取饮料中,请稍后购买。");
                break;
            case SOLD_OUT :
                System.out.println("饮料已售罄,不可购买。");
                break;
        }

    }

    /** 退款 */
    public void refund() {
        switch (this.state) {
            case NO_PAY :
                System.out.println("尚未支付,请不要乱按。");
                break;
            case PAY :
                System.out.println("退款成功。");
                this.state = NO_PAY;
                break;
            case SOLD :
                System.out.println("已购买,请取用。");
                break;
            case SOLD_OUT :
                System.out.println("饮料已售罄,不可购买。");
                break;
        }
    }

    /** 购买 */
    public void buy() {
        switch (this.state) {
            case NO_PAY :
                System.out.println("尚未支付,请不要乱按。");
                break;
            case PAY :
                System.out.println("购买成功,请取用。");
                this.state = SOLD;
                break;
            case SOLD :
                System.out.println("已购买,请取用。");
                break;
            case SOLD_OUT :
                System.out.println("饮料已售罄,不可购买。");
                break;
        }
    }

    /** 取货 */
    public void getGoods() {
        switch (this.state) {
            case NO_PAY :
                System.out.println("尚未支付,请不要乱按。");
                break;
            case PAY :
                System.out.println("已购买,请取用。");
            case SOLD :
                System.out.println("正在出货中,请等待三秒。");
                this.store--;
                if (this.store == 0) {
                    this.state = SOLD_OUT;
                } else {
                    this.state = NO_PAY;
                }
                break;
            case SOLD_OUT :
                System.out.println("饮料已售罄,不可购买。");
                break;
        }
    }
}

看到上面一大段代码,感觉很懵,如果现在需要扩展状态怎么办,需要对每一种操作下的方法进行修改,这将是一件麻烦的事情
如何能够灵活的扩展状态呢
四种操作是不会变的,但是状态是可以灵活变化的,下面改进代码:

改进代码

定义一个接口,每一种状态需要实现该接口:

public interface State {

    /** 支付 */
    void pay();

    /** 退款 */
    void refund();

    /** 购买 */
    void buy();

    /** 取货 */
    void getGoods();
}

未支付状态实现类:

public class NoPayState implements State {

    private NewDrinksSellingMatchine matchine;

    public NoPayState(NewDrinksSellingMatchine matchine) {
        this.matchine = matchine;
    }

    @Override
    public void pay() {
        System.out.println("支付成功,请确定购买饮料。");
        this.matchine.state = this.matchine.PAY;
    }

    @Override
    public void refund() {
        System.out.println("尚未支付,请不要乱按。");
    }

    @Override
    public void buy() {
        System.out.println("尚未支付,请不要乱按。");
    }

    @Override
    public void getGoods() {
        System.out.println("尚未支付,请不要乱按。");
    }
}

支付状态实现类:

public class PayState implements State{

    private NewDrinksSellingMatchine matchine;

    public PayState(NewDrinksSellingMatchine matchine) {
        this.matchine = matchine;
    }

    @Override
    public void pay() {
        System.out.println("已支付成功,请确定购买饮料。");
    }

    @Override
    public void refund() {
        System.out.println("退款成功。");
        this.matchine.state = this.matchine.NO_PAY;
    }

    @Override
    public void buy() {
        System.out.println("已购买,请取用。");
        this.matchine.state = this.matchine.SOLD;
    }

    @Override
    public void getGoods() {
        System.out.println("请先确定购买。");
    }
}

其他两种状态就不在这展示了,和上面两种差不多

public class NewDrinksSellingMatchine {

    final State NO_PAY, PAY, SOLD, SOLD_OUT;

    State state;

    int store;

    public NewDrinksSellingMatchine(int store) {
        NO_PAY = new NoPayState(this);
        PAY = new PayState(this);
        SOLD = new SoldState(this);
        SOLD_OUT = new SoldOutState(this);
        this.store = store;
        if(this.store > 0) {
            this.state = NO_PAY;
        }
    }

    public void pay() {
        this.state.pay();
    }

    public void refund() {
        this.state.refund();
    }

    public void buy() {
        this.state.buy();
    }

    public void getGoods() {
        this.state.getGoods();
    }
}

改进之后,如果需要对状态进行扩展,只需要实现State的接口就行了

状态模式

定义

一个类对外提供了多个行为,同时该类对象有多种状态,不同状态下对外的
行为的表现不同

意图

允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类

主要解决问题

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

何时使用

代码中包含大量与对象状态有关的条件语句

优缺点

优点:

  1. 封装了转换规则
  2. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
  3. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
  4. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

缺点:

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

类图:
在这里插入图片描述
涉及的角色:

  1. 抽象状态(State)角色:定义一个接口,用来封装环境(Context)对象的一个特定的状态所对应的行为
  2. 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为
  3. 环境(Context)角色:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例,这个具体状态类的实例给出此环境对象的现有状态

对应的代码如下:
State接口:

public interface State {

    void sampleOperation();
}

ConcreteState类:

public class ConcreteState implements State {
    @Override
    public void sampleOperation() {

    }
}

Context类:

public class Context {

    private State state;

    public void sampleOperation() {
        this.state.sampleOperation();
    }

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

曾侯乙编钟

曾侯乙编钟,1979年在湖北出土,一套共65件,总音域跨5个八度,12个半音齐全,每一只钟都发出不同的音
下面以曾侯乙编钟为例,熟悉一下状态模式:
在这里插入图片描述
每一只钟都需要实现的接口:

public interface ClockState {

    /** 打击钟 */
    void blow();

    void otherClock();
}

具体每一只钟的实现:

public class ClockConcreteStateC implements ClockState {
    @Override
    public void blow() {
        System.out.println("钟C被打击");
    }

    @Override
    public void otherClock() {
        System.out.println("钟A B没有被打击");
    }
}
public class ClockConcreteStateB implements ClockState {
    @Override
    public void blow() {
        System.out.println("钟B被打击");
    }

    @Override
    public void otherClock() {
        System.out.println("钟A C没有被打击");
    }
}
public class ClockConcreteStateA implements ClockState {
    @Override
    public void blow() {
        System.out.println("钟A被打击");
    }

    @Override
    public void otherClock() {
        System.out.println("钟B C没有被打击");
    }
}

曾候乙编钟,乐师选择一个钟打击它,每一只钟的每个音代表一个状态:

public class ClockContext {

    private ClockState state;

    public void blow() {
        this.state.blow();
        this.state.otherClock();
    }

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

测试类:

public class Test {
    public static void main(String[] args) {
        ClockContext context = new ClockContext();
        context.setState(new ClockConcreteStateA());
        context.blow();
    }
}

在这里插入图片描述
类图:
在这里插入图片描述
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个

状态模式-命令模式-策略模式

策略模式:侧重的是一个行为的多个算法实现,可互换算法,比如优惠活动满减和打折都是算法,可以选择其中一个来买买买

命令模式:侧重的是为多个行为提供灵活的执行方式,比如上面的奶茶,每个顾客购买奶茶的命令都是一个行为,而不同的奶茶制作方式不一样,则就需要灵活的去制作奶茶

状态模式:应用于状态机的情况

设计原则:

  • 区分变与不变,隔离变化
  • 面向接口编程
  • 多用组合,少用继承

posted on 2021-04-17 10:41  狐言不胡言  阅读(74)  评论(0编辑  收藏  举报