设计模式之状态模式
2015-07-17 21:44
429 查看
DesignPattern之State
1. Definition
==状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类==。这个模式将封装成为独立的类,并将动作委托到代表当前状态的对象,从而使行为会随着内部状态的改变而改变。
“看起来好像修改了它的类”的意思是:从客户的角度看来:如果说你使用的对象能完全改变它的行为,那么你就会觉得,这个对象实际上是从别的类实例化而来的。然而,==这是在使用状态模式,通过简单引用不同的状态对象来造成类改变的假象==。
UML(Head First Design Pattern p410)
Context: 可以拥有一些内部状态,里面有一个request方法,Context在不同状态下调用request方法发生的行为不同。不管是什么时候,只要调用request方法去,就会被委托到状态来处理。State接口: 定义了一个所有具体的状态的共同接口,任何状态都实现了这个相同的接口,这样一来,状态之间可以互相替换。
ConcreteState: 实现了State接口,处理来自Context的请求。每一个ConcreteState都提供了它自己对于请求的实现。所以当Context改变状态时行为也跟着改变。
个人理解:
Context在不同状态下调用request方法会产生不同的行为,所以调用这个方法必须得确定当前Context的状态是什么,才能产生正确的行为。
Too young too naive的设计:在request方法里加一大堆条件判断语句:
~~~java
if(State a) { // Operation A } else if (State b) { // Operation B } else { // Operation C }
~~~
或者用switch语句。这样做的画,设计就绑死在实现上了,如果改了需求(比如说在State a下要执行Operation B),那么就得修改源码,需求一变动,源码就得不断修改。显然这样做十分不符合OCP。
那么可以换个角度思考一下,既然Context在不同的状态下对request方法有不同的行为,何不把变化的部分给封装出来。把request方法的方法委托给状态去实现,不同的状态handle request的实现都不同,然后用Context去==组合==不同的状态。这样就能省去了在request方法里对不同状态的判断的条件语句,因为Context一旦转换状态,就组合它所需要的状态,状态的判断已经通过组合新状态的形式无形中完成了,在某个特定的状态下request方法由特定的实现。
2. Example
状态机如下:|----------| | | | Sold Out |<---------------------------------------| | | | |----------| [Num == 0] | | |-----------| |-------------| |-------| | | |----[Insert Quarter]-------> | | | | | | No Quarter| | Has Quarter |-----[turn crank]----> | Sold |--------| | |<---[eject Quarter]--------- | | | | | |-----------| | |<--------| |-------| | |-------------| | [Num > 0] | | -------------------------------|
Context:
GumballMachine
~~~java
package state; public class GumballMachine { private State soldOutState; private State noQuarterState; private State hasQuarterState; private State soldState; private State state = soldOutState; private int count = 0; public GumballMachine(int gumballNum) { this.soldOutState = new SoldOutState(this); this.noQuarterState = new NoQuarterState(this); this.hasQuarterState = new HasQuarterState(this); this.soldState = new SoldState(this); this.count = gumballNum; if (gumballNum > 0) { this.state = noQuarterState; } } public void insertQuarter() { this.state.insertQuarter(); } public void ejectQuarter() { this.state.ejectQuarter(); } public void turnCrank() { this.state.turnCrank(); this.state.dispense(); } void setState(State state) { this.state = state; } public void releaseBall() { System.out.println("A gumball is coming!!!\n"); if (this.count > 0) { this.count -= 1; } } public State getSoldOutState() { return soldOutState; } public void setSoldOutState(State soldOutState) { this.soldOutState = soldOutState; } public State getNoQuarterState() { return noQuarterState; } public void setNoQuarterState(State noQuarterState) { this.noQuarterState = noQuarterState; } public State getHasQuarterState() { return hasQuarterState; } public void setHasQuarterState(State hasQuarterState) { this.hasQuarterState = hasQuarterState; } public State getSoldState() { return soldState; } public void setSoldState(State soldState) { this.soldState = soldState; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public State getState() { return state; } @Override public String toString() { return "Gumball amonut : " + this.count + "\nMachine State : " + this.state + "\n"; } }
~~~
abstact State :
state
~~~java
package state; public abstract class State { protected GumballMachine machine; public State(GumballMachine machine) { this.machine = machine; } public abstract void insertQuarter(); public abstract void ejectQuarter(); public abstract void turnCrank(); public abstract void dispense(); }
~~~
Concrete State:
NoQuarter,
HasQuarter,
Sold,
SoldOut
NoQuarter
~~~java
package state; public class NoQuarterState extends State { public NoQuarterState(GumballMachine machine) { super(machine); } @Override public void insertQuarter() { System.out.println("u have inserted a quarter!\n"); this.machine.setState(this.machine.getHasQuarterState()); } @Override public void ejectQuarter() { System.out.println("u haven't inserted a quarter!\n"); } @Override public void turnCrank() { System.out.println("Sorry there's no quarter...\n"); } @Override public void dispense() { System.out.println("u need to pay first!\n"); } public String toString() { return "No Quarter"; } }
~~~
HasQuarter
~~~java
package state; public class HasQuarterState extends State { public HasQuarterState(GumballMachine machine) { super(machine); } @Override public void insertQuarter() { System.out.println("Can not insert another quarter!\n"); } @Override public void ejectQuarter() { System.out.println("Quarter returning...\n"); this.machine.setState(this.machine.getNoQuarterState()); } @Override public void turnCrank() { System.out.println("u turned...\n"); this.machine.setState(this.machine.getSoldState()); } @Override public void dispense() { System.out.println("No gumball dispensed!\n"); } public String toString() { return "Has Quarter"; } }
~~~
Sold
~~~java
package state; public class SoldState extends State { public SoldState(GumballMachine machine) { super(machine); } @Override public void insertQuarter() { System.out.println("Plz wait, we are giving u a gumball!\n"); } @Override public void ejectQuarter() { System.out.println("Sorry, u have already turned the crank!\n"); } @Override public void turnCrank() { System.out.println("Can not turn the crank twice!\n"); } @Override public void dispense() { this.machine.releaseBall(); if (this.machine.getCount() > 0) { this.machine.setState(this.machine.getNoQuarterState()); } else { System.out.println("Oops, out of gamballs...\n"); this.machine.setState(this.machine.getSoldOutState()); } } public String toString() { return "Sold"; } }
~~~
SoldOut
~~~java
package state; public class SoldOutState extends State { public SoldOutState(GumballMachine machine) { super(machine); } @Override public void insertQuarter() { System.out.println("Sorry, u can not insert a quarter. The machine is sold out!\n"); } @Override public void ejectQuarter() { System.out.println("Sorry, u haven't inserted a quarter yet\n"); } @Override public void turnCrank() { System.out.println("Sorry the machine is sold out!\n"); } @Override public void dispense() { System.out.println("Sorry the machine is sold out! No gumballs dispensed...\n"); } public String toString() { return "SoldOut"; } }
~~~
Test
~~~java
package state; public class TestGumballMachine { public static void main(String[] args) { GumballMachine machine = new GumballMachine(5); System.out.println(machine); System.out.println("-->Insert a quarter"); machine.insertQuarter(); System.out.println(machine); System.out.println("-->Turn the crank"); machine.turnCrank(); System.out.println(machine); } }
~~~
Result:
~~~java
Gumball amonut : 5 Machine State : No Quarter -->Insert a quarter u have inserted a quarter! Gumball amonut : 5 Machine State : Has Quarter -->Turn the crank u turned... A gumball is coming!!! Gumball amonut : 4 Machine State : No Quarter
~~~
分析
仅以操作insertQuarter来分析,GumballMachine在不同状态下对这个操作的实现不同:
NoQuarter状态:可以insert Quarter,打印一句话
"u have inserted a quarter!\n",GumballMachine状态转到HasQuarter
HasQuarter状态:不能再insert Quarter了—打印一句话:
"Can not insert another quarter!\n"
Sold状态:正在出Gumball,也不能insert Quarter,—-打印一句话
"Plz wait, we are giving u a gumball!\n"
SoldOut状态:也不能insert Quarter,—-打印一句话
"Sorry, u can not insert a quarter. The machine is sold out!\n"
如果不采用状态模式,那么久得在insertQuarter里加条件判断语句来判断当前状态,前面已经说过,这样设计不符合OCP,很不合理。
采用状态模式后,Gumballm类组合了一个抽象的State:
~~~java
private State state = soldOutState;
~~~
可以在GumballMachine这个类里看到
~~~java
public void insertQuarter() { this.state.insertQuarter(); }
~~~
当调用GumballMachine的insertQuarter方法时,实际上是委托给它的状态去实现;再观察上面四个具体的状态类,每个类对这个方法的实现都不相同。在转换状态的时候,通过:
~~~java
void setState(State state) { this.state = state; }
~~~
来组合不同的状态,从而达到转换状态的目的,也省掉了大段大段的判断状态的条件语句。
状态模式和策略模式
仔细观察不难发现,状态模式和策略模式的UMl图完全一样,这两个模式的差别在于他们的意图状态模式
将一群行为封装在状态对象中,contex的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态。因此,context的行为也会跟着改变。但是context的客户对于状态对象理解不多,甚至根本是浑然不觉。
一般把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,可以通过在context中简单的改变状态对象(组合不同的状态对象)来改变context的行为。
策略模式
对策略模式而言,客户通常主动指定context所要组合的策略对象是哪一个。
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果使用继承定义了一个类的行为,将会被这个行为困住,甚至想要修改它都难。但使用了策略模式,可以通过组合不同的对象来改变行为。
对状态模式的思考
在个别的状态中封装状态行为,结果就是:增加这个设计中类的数目。这就是为了要获取弹性而付出的代价,这算是状态模式的缺点。但是状态模式的设计时绝对值得的,因为真正重要的是==暴露给客户的类的数目==,而我们有办法将这些额外的状态类全都隐藏起来。(在上面例子的测试类中并没有出现任何状态类)。
如果不采用状态模式,不将这些状态封装在不同的对象中,就会得到巨大的、整块的条件语句。这会让代码变得十分不容易维护和理解。通过使用许多状态对象,可以让状态变得很干净,在以后理解和维护它们时,可以省下许多功夫。
另外,状态类客户还可以被多个context实例共享
相关文章推荐
- [POJ3982]序列[字符串][大整数]
- char与byte的区别
- Soldier and Bananas
- 使用Python的BeautifulSoup 类库采集网页内容
- 银行流水打印软件账单打印软件
- Ubuntu解决wifi无法连接的问题
- leetcode难度及面试频率
- 技术随笔八——仰望夜空 仰望Git
- 技术随笔八——仰望夜空 仰望Git
- 深入理解计算机系统读书笔记之第二章信息的表示和处理
- JSP路径出现故障
- JavaScript对象字面量
- c++primer之多维数组
- 关于dB,dBm的分析总结
- OpenStack Neutron理解
- android菜鸟学习笔记29----Android应用向用户发送提示信息的方式总结
- Vojos P1133装箱问题
- Codeblocks自动代码格式化
- JVM虚拟机概述
- Python3.4学习笔记(二)------逻辑操作符,循环体,分支