【设计模式 - 20】之状态模式(State)
1、模式简介
状态模式的定义:
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。这个模式将状态封装成独立的类,并将动作委托到代表当前状态的类的对象。
状态模式的优点:
- 封装了转换规则;
- 枚举可能的状态,在枚举状态之前需要确定状态种类;
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为;
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块;
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点:
- 状态模式的使用必然会增加系统类和对象的个数;
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱;
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
状态模式的适用场景:
- 当行为需要随着状态的改变而改变时;
- 当条件、分支语句很复杂冗长而需要优化时。
状态模式和策略模式:
- 状态模式和策略模式的相同点是它们都需要根据需求选择相应的状态或策略;
- 状态模式和策略模式的不同点是状态模式是在一个类中通过不同的动作切换不同的状态,而策略模式是为一个类选择某个策略,即状态模式中的Context是和多个状态关联的,而策略模式中的Context只和一个策略关联。
2、案例
在这个例子中,我们要实现一个糖果机的功能:
投入一元钱,转动摇把,就可以得到一颗糖果(当然是在及其中还有糖果的情况下);有10%的概率能够得到两颗糖果(被称为Winner)。具体代码如下:
状态接口State中的代码:
public interface State { // 投入一元钱 void onMoneyInserted(); // 将钱退回 void onMoneyReturned(); // 转动摇把 void onCrankTurned(); // 给出糖果 void onCandyPoped(); }
没有投币的状态NoMoneyState中的代码:
public class NoMoneyState implements State { private CandyMachine candyMachine; public NoMoneyState(CandyMachine candyMachine) { this.candyMachine = candyMachine; } @Override public void onMoneyInserted() { System.out.println("投币成功!"); candyMachine.setState(candyMachine.getMoneyInsertedState()); } @Override public void onMoneyReturned() { System.out.println("还没有投币!操作无效!"); } @Override public void onCrankTurned() { System.out.println("还没有投币!操作无效!"); } @Override public void onCandyPoped() { System.out.println("还没有投币!操作无效!"); } }
投币后的状态MoneyInsertedState中的代码:
public class MoneyInsertedState implements State { private CandyMachine candyMachine; public MoneyInsertedState(CandyMachine candyMachine) { this.candyMachine = candyMachine; } @Override public void onMoneyInserted() { System.out.println("不能多次投币!"); } @Override public void onMoneyReturned() { System.out.println("正在退钱......退钱成功!"); candyMachine.setState(candyMachine.getNoMoneyState()); } @Override public void onCrankTurned() { System.out.println("转动了摇把......"); int random = (int) (Math.random() * 10000); if (random % 10 == 0 && candyMachine.getCandyCount() > 1) { System.out.println("恭喜您获得了两颗糖果!"); candyMachine.setState(candyMachine.getWinnerState()); candyMachine.popCandy(); } else { if (candyMachine.getCandyCount() > 0) { candyMachine.setState(candyMachine.getCandyPoppingState()); candyMachine.popCandy(); } else { candyMachine.setState(candyMachine.getNoCandyState()); candyMachine.popCandy(); } } } @Override public void onCandyPoped() { System.out.println("请先转动摇把!"); } }
没有糖果的状态NoCandyState中的代码:
public class noCandyState implements State { private CandyMachine candyMachine; public noCandyState(CandyMachine candyMachine) { this.candyMachine = candyMachine; } @Override public void onMoneyInserted() { System.out.println("已经没有糖果了!"); onMoneyReturned(); } @Override public void onMoneyReturned() { System.out.println("投币退回!"); candyMachine.setState(candyMachine.getNoMoneyState()); } @Override public void onCrankTurned() { System.out.println("已经没有糖果了!操作无效"); onMoneyReturned(); } @Override public void onCandyPoped() { System.out.println("已经没有糖果了!"); onMoneyReturned(); } }
正在发放糖果的状态CandyPoppingState中的代码:
public class CandyPoppingState implements State { private CandyMachine candyMachine; public CandyPoppingState(CandyMachine candyMachine) { this.candyMachine = candyMachine; } @Override public void onMoneyInserted() { System.out.println("正在发放糖果,请勿操作!"); } @Override public void onMoneyReturned() { System.out.println("正在发放糖果,请勿操作!"); } @Override public void onCrankTurned() { System.out.println("正在发放糖果,请勿操作!"); } @Override public void onCandyPoped() { candyMachine.setCandyCount(candyMachine.getCandyCount() - 1); System.out.println("成功发放了一颗糖果!"); candyMachine.setState(candyMachine.getNoMoneyState()); } }
获得两颗糖果的状态WinnerState中的代码:
public class WinnerState implements State { private CandyMachine candyMachine; public WinnerState(CandyMachine candyMachine) { this.candyMachine = candyMachine; } @Override public void onMoneyInserted() { System.out.println("正在处理,请勿操作!"); } @Override public void onMoneyReturned() { System.out.println("正在处理,请勿操作!"); } @Override public void onCrankTurned() { System.out.println("正在处理,请勿操作!"); } @Override public void onCandyPoped() { candyMachine.setCandyCount(candyMachine.getCandyCount() - 2); System.out.println("正在发放第一颗糖果......"); System.out.println("正在发放第二颗糖果......"); System.out.println("糖果发放成功!"); candyMachine.setState(candyMachine.getNoMoneyState()); } }
糖果机类CandyMachine中的代码:
public class CandyMachine { private State moneyInsertedState; private State noMoneyState; private State noCandyState; private State candyPoppingState; private State winnerState; private State state; private int candyCount; public CandyMachine(int candyCount) { this.moneyInsertedState = new MoneyInsertedState(this); this.noMoneyState = new NoMoneyState(this); this.noCandyState = new noCandyState(this); this.candyPoppingState = new CandyPoppingState(this); this.winnerState = new WinnerState(this); this.state = noMoneyState; this.candyCount = candyCount; } // 投币 public void insertMoney() { this.state.onMoneyInserted(); } // 退钱 public void returnMoney() { this.state.onMoneyReturned(); } // 转动摇把 public void turnCrank() { this.state.onCrankTurned(); } // 发放糖果 public void popCandy() { this.state.onCandyPoped(); } public State getMoneyInsertedState() { return moneyInsertedState; } public void setMoneyInsertedState(State moneyInsertedState) { this.moneyInsertedState = moneyInsertedState; } public State getNoMoneyState() { return noMoneyState; } public void setNoMoneyState(State noMoneyState) { this.noMoneyState = noMoneyState; } public State getNoCandyState() { return noCandyState; } public void setNoCandyState(State noCandyState) { this.noCandyState = noCandyState; } public State getCandyPoppingState() { return candyPoppingState; } public void setCandyPoppingState(State candyPoppingState) { this.candyPoppingState = candyPoppingState; } public State getWinnerState() { return winnerState; } public void setWinnerState(State winnerState) { this.winnerState = winnerState; } public State getState() { return state; } public void setState(State state) { this.state = state; } public int getCandyCount() { return candyCount; } public void setCandyCount(int candyCount) { this.candyCount = candyCount; } }
测试类Test中的代码:
public class Test { public static void main(String[] args) { CandyMachine candyMachine = new CandyMachine(5); candyMachine.insertMoney(); candyMachine.turnCrank(); candyMachine.insertMoney(); candyMachine.turnCrank(); candyMachine.insertMoney(); candyMachine.turnCrank(); candyMachine.insertMoney(); candyMachine.turnCrank(); candyMachine.insertMoney(); candyMachine.turnCrank(); candyMachine.insertMoney(); candyMachine.turnCrank(); } }
运行结果如下图所示(左图是每次都获得一颗糖果的情况;右图是有一次获得了两颗糖果的情况):
最后贴出状态模式的GitHub代码地址:【GitHub - State】。