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,状态模式允许状态机随着状态的改变而改变行为,状态的转换可以由状态机决定,也可以由状态对象决定