《HF 设计模式》 C10 状态模式

1 模拟糖果机

策略模式是围绕可以互换的算法来创建业务,而状态模式是通过改变对象内部的状态来帮对象控制自己的行为

1.1 糖果机需求

现在需要用Java程序来控制糖果机的工作,需要设计一个良好的可维护有弹性的糖果机系统,糖果机的工作过程如下:
在这里插入图片描述
上面的需求图是一张状态图,每个圆圈是一个状态,每个箭头是状态的转换要从一个状态进入到另一个状态,必须发生一些“动作”

如在“没有25分钱”的状态时,投入25分钱,就会进入到“有25分钱”的状态,接着用户就可以选择转动曲柄或按下退款按钮,来进行后续的操作…

对于任何一个可能的动作,都需要缜密检查,看看状态和动作是否合适

1.2 简单糖果机

了解了状态图后,简单模拟一下糖果机内行为带来的状态改变:
在这里插入图片描述
我们通过创建一个实例遍历来持有状态值,并在方法内书写条件代码来处理不同的状态

/**
 * @author 雫
 * @date 2021/3/10 - 15:13
 * @function 糖果机
 */
public class Machine1 {
    private static final int NO_MONEY = 0;
    private static final int HAS_MONEY = 1;
    private static final int BACK_MONEY = 2;

    private int state = NO_MONEY;
    private int count = 0;

    public Machine1(int count) {
        this.count = count;
        if(count > 0) {
            this.state = NO_MONEY;
        }
    }

    public void insertMoney() {
        if(this.state == NO_MONEY) {
            System.out.println("已投币");
            this.state = HAS_MONEY;
            System.out.println("请点击 <购买> 或 <退款> 按钮");
            return;
        } else if(this.state == HAS_MONEY) {
            this.state = BACK_MONEY;
            System.out.println("请不要连续投币,马上会退出您的硬币");
            backMoney();
            return;
        }
    }

    public void pressBackMoneyButton() {
        if(this.state == HAS_MONEY) {
            System.out.println("马上会为您退款");
            backMoney();
        } else {
            System.out.println("尚未投币");
        }
    }

    public void pressBuyButton() {
        if(this.state == HAS_MONEY) {
            System.out.println("请稍后,正在查询糖果机剩余糖果量...");
            if(checkCount()) {
                System.out.println("正在为您准备糖果...");
                prepareCandy();
            } else {
                System.out.println("没有糖果了,正在为您退款");
                this.state = BACK_MONEY;
                backMoney();
            }
        } else {
            System.out.println("请投币后再按下购买按钮");
        }
    }

    private void backMoney() {
        if(this.state == BACK_MONEY) {
            System.out.println("已退款");
            this.state = NO_MONEY;
        }
    }

    private boolean checkCount() {
        if(this.count > 0) {
            return true;
        } else {
            return false;
        }
    }

    private void prepareCandy() {
        System.out.println("请拿走您的糖果");
        this.count--;
        this.state = NO_MONEY;
    }

}

测试:
在这里插入图片描述

1.3 增加需求

现在模拟的糖果机可以简单工作了,但是现在有了新的需求,要求每当有人购买糖果时,有10%的概率可以多得到一颗糖果

回顾刚才的代码,如果我们要新加入这个抽奖功能,就需要新的状态,并且在多处更改代码,增加新的if语句来判断等等…
且一旦有了新需求,源码就将不断地被更改,直到它变得一团糟,先前的做法不但违反了开闭原则,并且系统没有弹性,充斥着if-else,逻辑混乱,我们需要重构糖果机的代码

新的设计:
1,定义一个state接口,在这个接口内,糖果机的每个动作都有一个对应得方法
2,为机器中的每个状态实现state接口,这些类负责在对应状态下控制机器的行为
3,将糖果机的动作委托到状态类

即我们现在把一个状态的所有行为放在一个类中,这样我们就将行为局部化了,通过组合完成使得代码可扩展,易维护

1.4 重新设计糖果机代码

设计一个State接口,糖果机中每个状态对应一个状态类,每个状态类都要实现State接口

糖果机:

/**
 * @author 雫
 * @date 2021/3/10 - 16:06
 * @function 糖果机
 */
public class Machine {
    private State soldOutState;
    private State noMoneyState;
    private State hasMoneyState;
    private State soldState;

    private State state;
    private int count = 0;

    public Machine(int count) {
        this.soldOutState = new SoldOutState(this);
        this.noMoneyState = new NoMoneyState(this);
        this.hasMoneyState = new HasMoneyState(this);
        this.soldState = new SoldState(this);

        this.count = count;
        if(count > 0) {
            this.state = noMoneyState;
        }
    }

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

    int getCount() {
        return count;
    }

    State getSoldOutState() {
        return soldOutState;
    }

    State getNoMoneyState() {
        return noMoneyState;
    }

    State getHasMoneyState() {
        return hasMoneyState;
    }

    State getSoldState() {
        return soldState;
    }

    public void insertMoney() {
        state.insertMoney();
    }

    public void ejectMoney() {
        state.ejectMoney();
    }

    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

    void releaseCandy() {
        System.out.println("正在准备你的糖果");
        if(this.count != 0) {
            count--;
        }
        System.out.println("请取出你的糖果");
    }

}

状态:

/**
 * @author 雫
 * @date 2021/3/10 - 16:01
 * @function 状态接口
 */
public interface State {
    //投币
    void insertMoney();

    //退款
    void ejectMoney();

    //转动曲柄
    void turnCrank();

    //分发糖果
    void dispense();
}


/**
 * @author 雫
 * @date 2021/3/10 - 16:05
 * @function 没有钱投入机器时的糖果机状态
 */
public class NoMoneyState implements State {
    private Machine machine;

    public NoMoneyState(Machine machine) {
        this.machine = machine;
    }

    @Override
    public void insertMoney() {
        System.out.println("你投入了一枚硬币");
        machine.setState(machine.getHasMoneyState());
    }

    @Override
    public void ejectMoney() {
        System.out.println("尚未投币,无法退款");
    }

    @Override
    public void turnCrank() {
        System.out.println("尚未投币,无法转动曲柄");
    }

    @Override
    public void dispense() {
        System.out.println("尚未投币,无法获取糖果");
    }

}

/**
 * @author 雫
 * @date 2021/3/10 - 16:05
 * @function 当有钱投入机器时糖果机的状态
 */
public class HasMoneyState implements State {
    private Machine machine;

    public HasMoneyState(Machine machine) {
        this.machine = machine;
    }

    @Override
    public void insertMoney() {
        System.out.println("请不要连续投币");
    }

    @Override
    public void ejectMoney() {
        System.out.println("正在退款...");
        System.out.println("已退款");
        machine.setState(machine.getNoMoneyState());
    }

    @Override
    public void turnCrank() {
        System.out.println("正在转动曲柄");
        machine.setState(machine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("暂时无法分发糖果");
    }

}
...

这样设计后糖果机的工作方式:
在这里插入图片描述

状态机(糖果机)有许多种状态,但每种状态下相同行为的结果却不一样,通过组合的方式,让各种状态类成为状态机的成员,让状态机能够在不同的状态下,执行不同状态所对应的操作,这里的组合是双向的,状态机也应该成为状态类的成员,从而让状态类执行完操作后,更改状态机的操作

状态机中的各种行为是被委托给状态类进行的

1.5 定义状态模式

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

我们将不同的状态封装成了独立的类,并将动作委托到代表当前状态的对象,因此行为会随着内部状态的改变而改变
在这里插入图片描述

State可以是接口,也可以是抽象类,如果各个不同状态有相同方法,使用抽象类更为方便

状态模式看起来很像策略模式,它们都通过组合来扩展自己的功能,但二者的意图却完全不同:
状态模式封装基于状态的行为,并将其委托到当前状态
策略模式将可以互换的行为封装起来,然后使用委托的方法,决定使用哪一个行为

虽然使用状态模式,让我们的代码中增加了一些新的类,但这些并不是冗余的代码,我们同时也遵循了 单一职责原则,让每个类都更易维护

1.6 状态模式小结

1,状态模式允许一个对象基于内部状态而拥有不同的行为

2,状态机会将行为委托给当前状态对象

3,通过把每个状态封装进一个类,就可以把以后需要做的任何改变局部化

4,状态模式允许状态机随着状态的改变而改变行为,状态的转换可以由状态机决定,也可以由状态对象决定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值