【0】README
0.1)本文部分文字描述转自 “head first设计模式”,旨在学习 事务的状态(状态模式) 的基础知识;
【1】应用场景一
1.1)还记得成都市各大高校内的米源自动售卖机吗?售卖机的主要制造商发现,只要把CPU 放入机器,可以提高销量。于是乎,它们提供了一幅自动售卖机的状态图给我们,希望我们用java 帮他实现,且代码富有弹性易于扩展(下面以米源糖果售卖机为例给出状态图);
1.2)具体实现(for downloading, please visit https://github.com/pacosonTang/HeadFirstDesignPattern/tree/master/state_pattern_10/chapter10)
- step1)米源售卖机实现
public class CandyMachine { private final static int SOLD_OUT = 0; // 售罄状态 private final static int NO_QUARTER = 1; // 无币状态,QUARTER==25美分==币 private final static int HAS_QUARTER = 2; // 有币状态 private final static int SOLD = 3; // 售卖状态 private int state = SOLD_OUT; private int count = 0; public CandyMachine(int count) { this.count = count; if(count > 0) { this.state = NO_QUARTER; } } public void insertQuarter() { // client投币请求 if(state == HAS_QUARTER) { System.out.println("you can't insert another quarter."); } else if(state == NO_QUARTER) { System.out.println("you insert a quarter."); state = HAS_QUARTER; } else if(state == SOLD_OUT) { System.out.println("you can't insert a quarter for the machine is sold out."); } else if(state == SOLD) { System.out.println("please wait, we're already gibing you a candy."); } } public void ejectQuarter() { // client请求退钱 if(state == HAS_QUARTER) { System.out.println("quarter returned."); state = NO_QUARTER; } else if(state == NO_QUARTER) { System.out.println("you haven't inserted a quarter."); } else if(state == SOLD_OUT) { System.out.println("you can't eject for you haven't inserted a quarter yet."); } else if(state == SOLD) { System.out.println("sorry, you already trune the crank."); } } public void turnCrank() { // client转动曲柄动作 if(state == HAS_QUARTER) { System.out.println("you turned, please wait...."); state = SOLD; dispense(); } else if(state == NO_QUARTER) { System.out.println("you turned but there is no quarter."); } else if(state == SOLD_OUT) { System.out.println("you truned but there's no candy."); } else if(state == SOLD) { System.out.println("turning twice doesn't get you another candy."); } } private void dispense() { // 分发糖果 if(state == HAS_QUARTER) { System.out.println("no candy dispensed."); } else if(state == NO_QUARTER) { System.out.println("you need to insert a quarter first."); } else if(state == SOLD_OUT) { System.out.println("no candy dispensed."); } else if(state == SOLD) { System.out.println("a candy comes rolling out the slot."); count--; if(count == 0) { System.out.println("Oops, there's no candy."); state = SOLD_OUT; } else { state = NO_QUARTER; } } } @Override public String toString() { String state_str = null; switch (state) { case SOLD_OUT: state_str = "SOLD_OUT"; break; case SOLD: state_str = "SOLD"; break; case NO_QUARTER: state_str = "NO_QUARTER"; break; case HAS_QUARTER: state_str = "HAS_QUARTER"; break; } return "=== I own " + count + " candies, and stay in " + state_str + " state. ==="; } }
- step2)测试用例
public class CandyMachineTest { public static void main(String[] args) { CandyMachine cm = new CandyMachine(5); System.out.println(cm); System.out.println("====== 1st test: ======"); cm.insertQuarter(); cm.turnCrank(); System.out.println(cm); System.out.println("====== 2nd test: ======"); cm.insertQuarter(); cm.ejectQuarter();//要求售卖机退钱 cm.ejectQuarter();//要求售卖机第二次退钱 System.out.println("====== 3rd test: ======"); cm.insertQuarter(); cm.ejectQuarter(); cm.turnCrank();// 请求退钱后,不应该得到糖果 System.out.println(cm); } }
- step3)打印结果
=== I own 5 candies, and stay in NO_QUARTER state. === ====== 1st test: ====== you insert a quarter. you turned, please wait.... a candy comes rolling out the slot. === I own 4 candies, and stay in NO_QUARTER state. === ====== 2nd test: ====== you insert a quarter. quarter returned. you haven't inserted a quarter. ====== 3rd test: ====== you insert a quarter. quarter returned. you turned but there is no quarter. === I own 4 candies, and stay in NO_QUARTER state. ===
【2】应用场景二(新的需求: 当曲柄被转动时,有10%的几率掉下来的是两个糖果,即有10%的可能性买一送一)
2.1)新需求下的米源售卖机实现 (for downloading, please visit https://github.com/pacosonTang/HeadFirstDesignPattern/tree/master/state_pattern_10/chapter10_1)
- step1)定义一个状态接口(State),在这个接口内,售卖机的每个动作有一个对应方法;
public abstract class State { protected String name; public abstract void insertQuarter(); public abstract void ejectQuarter(); public abstract void trunCrank(); public abstract void dispense(); public String getName() { return name; } }
- step2)创建该状态接口的子类,实现售卖机的相应方法;
// 售罄状态 public class SoldOutState extends State { CandyMachine machine; public SoldOutState(CandyMachine machine) { super.name = "SoldOutState"; this.machine = machine; } @Override public void insertQuarter() { System.out.println("you can't insert a quarter for there's no candies."); } @Override public void ejectQuarter() { System.out.println("you have not inserted a quarter."); } @Override public void trunCrank() { System.out.println("you turned but there is no quarter."); } @Override public void dispense() { System.out.println("you need to insert a quarter first."); } }
public class NoQuarterState extends State { CandyMachine machine; public NoQuarterState(CandyMachine machine) { super.name = "NoQuarterState"; this.machine = machine; } @Override public void insertQuarter() { System.out.println("you insert a quatter."); machine.setState(machine.getHasQuarterState()); } @Override public void ejectQuarter() { if(machine.getState() == machine.getHasQuarterState()) { System.out.println("returned a quarter."); machine.setState(machine.getNoQuarterState()); } else { System.out.println("you have not inserted a quarter."); } } @Override public void trunCrank() { System.out.println("you turned but there is no quarter."); } @Override public void dispense() { System.out.println("you need to insert a quarter first."); } }
// 有币状态 public class HasQuarterState extends State { Random random = new Random(); CandyMachine machine; public HasQuarterState(CandyMachine machine) { super.name = "HasQuarterState"; this.machine = machine; } @Override public void insertQuarter() { System.out.println("you can't insert another quarter."); } @Override public void ejectQuarter() { // client退钱请求 System.out.println("quarter returned."); machine.setState(machine.getNoQuarterState()); } @Override public void trunCrank() { // 有币状态下,进行几率性中奖测试 System.out.println("you turned , please waiting......"); int winner = random.nextInt(5); System.out.println("\nrandom winner == " + winner); if(winner == 2 && machine.getCount() > 1) { machine.setState(machine.getWinnerState()); } else { machine.setState(machine.getSoldState()); } } @Override public void dispense() { System.out.println("no candies dispensed."); } }
// 售卖状态 public class SoldState extends State { CandyMachine machine; public SoldState(CandyMachine machine) { super.name = "SoldState"; this.machine = machine; } @Override public void insertQuarter() { System.out.println("please wait, we're already giving you a candy."); } @Override public void ejectQuarter() { System.out.println("sorry, you've already turned the crank."); } @Override public void trunCrank() { System.out.println("turning twice doesn't get you another candy."); } @Override public void dispense() { machine.releaseBall(); if(machine.getCount() > 0) { machine.setState(machine.getNoQuarterState()); } else { machine.setState(machine.getSoldOutState()); } } }
// 中奖状态 public class WinnerState extends State { CandyMachine machine; public WinnerState(CandyMachine machine) { this.machine = machine; } @Override public void insertQuarter() { System.out.println("error."); } @Override public void ejectQuarter() { // client退钱请求 System.out.println("error."); } @Override public void trunCrank() { System.out.println("error."); } @Override public void dispense() { System.out.println("you're a winner. you get 2 candies for your quarter."); machine.releaseBall(); if(machine.getCount() == 0) { machine.setState(machine.getSoldOutState()); } else { machine.releaseBall(); if(machine.getCount() > 0) { machine.setState(machine.getNoQuarterState()); } else { System.out.println("Oops, out of candies."); machine.setState(machine.getSoldOutState()); } } } }
- step3)将动作委托到状态类(构造米源糖果售卖机);
step4)测试用例public class CandyMachine { private State winnerState; //中奖状态 private State soldOutState; // 售罄状态 private State noQuarterState; // 无币状态,QUARTER==25美分==币 private State hasQuarterState; // 有币状态 private State soldState; // 售卖状态 private State state; private int count = 0; public CandyMachine(int count) { soldOutState = new SoldOutState(this); soldState = new SoldState(this); hasQuarterState = new HasQuarterState(this); noQuarterState = new NoQuarterState(this); winnerState = new WinnerState(this); state = soldOutState; this.count = count; if(count > 0) { state = noQuarterState; } } public void insertQuarter() { // client投币请求 state.insertQuarter(); } public void ejectQuarter() { // client请求退钱 state.ejectQuarter(); } public void turnCrank() { // client转动曲柄动作 state.trunCrank(); state.dispense(); } public void releaseBall() { // 释放糖果 System.out.println("a candy comes rolling out the slot...."); if(count != 0) { count--; } } public State getState() { return state; } public void setState(State state) { this.state = state; } public State getSoldOutState() { return soldOutState; } public void setSoldOutState(State soldOutState) { this.soldOutState = soldOutState; } public State getSoldState() { return soldState; } public void setSoldState(State soldState) { this.soldState = soldState; } public State getHasQuarterState() { return hasQuarterState; } public void setHasQuarterState(State hasQuarterState) { this.hasQuarterState = hasQuarterState; } public State getNoQuarterState() { return noQuarterState; } public void setNoQuarterState(State noQuarterState) { this.noQuarterState = noQuarterState; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public State getWinnerState() { return winnerState; } public void setWinnerState(State winner) { this.winnerState = winner; } @Override public String toString() { String state_str = null; switch (state.getName()) { case "SoldOutState": state_str = "SOLD_OUT"; break; case "SoldState": state_str = "SOLD"; break; case "NoQuarterState": state_str = "NO_QUARTER"; break; case "HasQuarterState": state_str = "HAS_QUARTER"; break; } return "=== I own " + count + " candies, and stay in " + state_str + " state. ==="; } }
public class CandyMachineTest { public static void main(String[] args) { CandyMachine cm = new CandyMachine(50); System.out.println(cm); System.out.println("====== init state ======"); System.out.println(cm); // 第一次测试 cm.insertQuarter(); cm.turnCrank(); System.out.println(cm); // 第二次测试 cm.turnCrank(); System.out.println(cm); // 第3次测试: 在为投币的情况下退币请求 cm.ejectQuarter(); System.out.println(cm); System.out.println("\n\n ====== 下面进入循环测试(中奖)随机数==2表示中奖 ======"); for (int i = 0; i < 5; i++) { cm.insertQuarter(); cm.turnCrank(); System.out.println(cm); } } }
- step5)打印结果
=== I own 50 candies, and stay in NO_QUARTER state. === ====== init state ====== === I own 50 candies, and stay in NO_QUARTER state. === you insert a quatter. you turned , please waiting...... random winner == 0 a candy comes rolling out the slot.... === I own 49 candies, and stay in NO_QUARTER state. === you turned but there is no quarter. you need to insert a quarter first. === I own 49 candies, and stay in NO_QUARTER state. === you have not inserted a quarter. === I own 49 candies, and stay in NO_QUARTER state. === ====== 下面进入循环测试(中奖,为了演示方便,我这里的中奖率为20%,且 <span style="font-family: Arial, Helvetica, sans-serif;">随机数==2表示中奖</span><span style="font-family: Arial, Helvetica, sans-serif;">) ======</span> you insert a quatter. you turned , please waiting...... random winner == 2 you're a winner. you get 2 candies for your quarter. a candy comes rolling out the slot.... a candy comes rolling out the slot.... === I own 47 candies, and stay in NO_QUARTER state. === you insert a quatter. you turned , please waiting...... random winner == 4 a candy comes rolling out the slot.... === I own 46 candies, and stay in NO_QUARTER state. === you insert a quatter. you turned , please waiting...... random winner == 4 a candy comes rolling out the slot.... === I own 45 candies, and stay in NO_QUARTER state. === you insert a quatter. you turned , please waiting...... random winner == 2 you're a winner. you get 2 candies for your quarter. a candy comes rolling out the slot.... a candy comes rolling out the slot.... === I own 43 candies, and stay in NO_QUARTER state. === you insert a quatter. you turned , please waiting...... random winner == 0 a candy comes rolling out the slot.... === I own 42 candies, and stay in NO_QUARTER state. ===
【3】状态模式介绍
1)基本常识:策略模式和状态模式是双胞胎,在出生时才分开;
2)策略模式和状态模式:策略模式是围绕可以互换的算法来创建成功业务的;状态模式通过改变对象的内部状态来帮助对象控制自己的行为;
3)状态模式定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类;
4)因为这个模式将状态封装为独立的类,并将动作委托到代表当前状态的类,我们知道行为会随着内容部状态而改变;
【4】状态模式和策略模式的区别
4.1)状态模式:我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流失,当前状态在状态对象集合中游走改变,以反映出 context内部的状态,因此,context的行为也会跟着改变;
4.2)策略模式:客户通常主动指定Context 所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略。但对于某个context对象来说,通常都只有一个 最适当的策略对象。
4.3)Conclusion:
- C1)一般而言,我们把策略模式想成是除了继承外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为所困住,甚至要修改他都很难。有了策略模式,你可以通过组合不同对象来改变行为;
- C2)我们把状态模式想成是不用在 context 中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过 context 内简单地修改状态对象来改变context 的行为;