状态模式(事物的状态)

来源:互联网 发布:淘宝怎么找网红做推广 编辑:程序博客网 时间:2024/05/27 01:06

基本常识:策略模式和状态模式是双胞胎,在出生的时候才分开。策略模式时围绕可以互换的算法来创建成功业务的。然而,状态走的是更崇高的一条路,它通过改变对象内部的状态来帮助对象控制自己的行为


一个通用的技巧:通过对对象内部的状态建模------通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同的状态。

具体的操作流程:

1,首先,找出所有的状态。

不画图了,用()代替圆圈。

(没有25分钱) (有25分钱) (糖果售罄) (售出糖果)

2,创建一个实例变量来持有目前的状态,然后定义每个状态的值:

final static int SOLD_OUT = 0;

final static int NO_QUARTER = 1;

final static int HAS_QUARTER = 2;

final static int SOLD = 3;


int state = SOLD_OUT;//设置初始状态。

3,现在,我们将所有系统中可以发生的动作整合起来,

投入25分钱, 退回25分钱,转动手柄,发放糖果。

任何移动动作都会造成状态的改变。除了发放糖果是糖果机内部的动作,机器自己调用自己,其他的动作是糖果机的接口,这是你能对糖果机做的事情。


4,现在,我们创建一个类,它的作用就像是一个状态机。对每一个动作,我们都创建一个方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如投币的动作:

    public void insertQuarter() {         if (state == HAS_QUARTER) {             System.out.println("You can't insert another quarter");         } else if (state == NO_QUARTER) {             state = HAS_QUARTER;             System.out.println("You inserted a quarter");         } else if (state == SOLD_OUT) {             System.out.println("You can't insert a quarter, the machine is sold out");         } else if (state == SOLD) {             System.out.println("Please wait, we're already giving you a gumball");         }     }

上述的第一版可以正常工作,但是如果增加一个新的状态就要增加好多的ifelse的判断,代码难以维护。


应该试着局部化每个状态的行为,这样一来,如果我们针对某个状态做出了改变,就不会把其他的代码给搞混乱了,换句话说,遵守“封装变化的原则”,如果我们将每个状态的行为都放在各自的类中,那么每个状态只要实现它自己的动作就可以了。糖果机只需要委托给代表当前状态的状态对象。这个符合“多用组合,少用继承”的原则。


新的设计

1,首先,我们定义一个state接口。在这个接口内,糖果机的每个动作都有一个对应的方法。

2,然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器的行为。

3,最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。

,1,定义状态接口和类

首先,让我们创建一个state接口,所有的状态都必须实现这个接口。(我觉得主要的改变是以方法为主导,而不是以状态为主导)

public interface State {     public void insertQuarter();     public void ejectQuarter();     public void turnCrank();     public void dispense(); }

然后将设计中的每个状态都封装成一个类,每个都实现state接口。



2,实现状态类:

例如,没有投币的状态NoQuarterState

public class NoQuarterState implements State {     GumballMachine gumballMachine;     public NoQuarterState(GumballMachine gumballMachine) {         this.gumballMachine = gumballMachine;     }     public void insertQuarter() {         System.out.println("You inserted a quarter");         gumballMachine.setState(gumballMachine.getHasQuarterState());//状态转移     }     public void ejectQuarter() {         System.out.println("You haven't inserted a quarter");     }     public void turnCrank() {         System.out.println("You turned, but there's no quarter");      }     public void dispense() {         System.out.println("You need to pay first");     }     public String toString() {         return "waiting for quarter";     } }

完整的糖果机的类

public class GumballMachine {     State soldOutState; //所有的状态都在这里    State noQuarterState;     State hasQuarterState;     State soldState;     State state = soldOutState;//当前状态     int count = 0;     public GumballMachine(int numberGumballs) {//开始的时候就用这个糖果机的类创建好各个状态以便后边的时候         soldOutState = new SoldOutState(this); //每个状态都创建一个状态的实例        noQuarterState = new NoQuarterState(this);         hasQuarterState = new HasQuarterState(this);         soldState = new SoldState(this);        this.count = numberGumballs;          if (numberGumballs > 0) {             state = noQuarterState;         }     }     public void insertQuarter() {         state.insertQuarter(); //现在,这些动作变得很容易实现了,我们只是委托到当前的状态。    }     public void ejectQuarter() {         state.ejectQuarter();     }     public void turnCrank() {         state.turnCrank();         state.dispense();     }    void setState(State state) {         this.state = state; //状态对象可以重新设置状态    }     void releaseBall() {         System.out.println("A gumball comes rolling out the slot...");         if (count != 0) {             count = count - 1;         }     }     int getCount() {         return count;     }     void refill(int count) {         this.count = count;         state = noQuarterState;     }    public State getState() {         return state;     }    public State getSoldOutState() {         return soldOutState;     }    public State getNoQuarterState() {         return noQuarterState;     }    public State getHasQuarterState() {         return hasQuarterState;     }    public State getSoldState() {         return soldState;     }     public String toString() {             } }


状态模式的定义

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

第一部分附有相当多的涵义,因为这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,我们知道行为会伴随内部状态而改变。糖果机提供了一个很好的例子,当糖果机是在NoQuarterState和HasQuarterState两个不同的状态时,你投入了25分钱,就会得到不同的行为。(接受和拒绝)

第二部分呢,一个对象“看起来好像修改了它的类”是什么意思?从客户的视角来看:如果说你使用的对象能够完成改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

状态模式的类图:



状态模式和策略模式的类图是相同的,它们的意图是不同的。

以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。

而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略。但对于某个context的对象来说,通常只有一个最适合的策略对象。比方说,第一章中,有些鸭子(例如绿头鸭)被设置成利用典型飞翔行为进行飞翔,而有些鸭子(例如橡皮鸭)使用的飞翔行为只能让他们紧贴地面。


一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,设置要修改它都很难。有了策略模式,你可以通过组合不太的对象来改变行为。

我们把状态模式想成是不用再Context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单的修改状态对象来改变context的行为。


需要改进的地方:

1,我们在售出糖果和赢家的状态中,有许多重复的代码。我们必须把这部分清理一下,可以把State设计成抽象类,然后把方法的默认行为放在其中。所以,所有的“错误响应”行为都可以写得具有通用性,并放在抽象的state类中供子类继承。

2,dispense()方法即使是在没有25分钱时曲柄被转动的情况下也总是会被调用。我们可以轻易地修改这部分,做法是让turnCrank()返回一个布尔值,或者引入异常(要么处理,要么抛出给外部处理)。


在实际使用,类似开关一样的状态切换是很多的,但有时并不是那么明显,取决于你的经验和对系统的理解深度.

这里要阐述的是"开关切换状态" 和" 一般的状态判断"是有一些区别的, " 一般的状态判断"也是有 if..elseif结构,例如:

    if (which==1) state="hello";
    else if (which==2) state="hi";
    else if (which==3) state="bye";

这是一个 " 一般的状态判断",state值的不同是根据which变量来决定的,which和state没有关系.如果改成:

    if (state.euqals("bye")) state="hello";
    else if (state.euqals("hello")) state="hi";
    else if (state.euqals("hi")) state="bye";

这就是 "开关切换状态",是将state的状态从"hello"切换到"hi",再切换到""bye";在切换到"hello",好象一个旋转开关,这种状态改变就可以使用State模式了.

具体的例子:

例如: 银行帐户, 经常会在Open 状态和Close状态间转换.

例如: 经典的TcpConnection, Tcp的状态有创建 侦听 关闭三个,并且反复转换,其创建 侦听 关闭的具体行为不是简单一两句就能完成的,适合使用State

例如:信箱POP帐号, 会有四种状态, start HaveUsername Authorized quit,每个状态对应的行为应该是比较大的.适合使用State

例如:在工具箱挑选不同工具,可以看成在不同工具中切换,适合使用State.如 具体绘图程序,用户可以选择不同工具绘制方框 直线 曲线,这种状态切换可以使用State.

状态模式优点:
(1) 封装转换过程,也就是转换规则
(2) 枚举可能的状态,因此,需要事先确定状态种类。

状态模式可以允许客户端改变状态的转换行为,而状态机则是能够自动改变状态,状态机是一个比较独立的而且复杂的机制,具体可参考一个状态机开源项目:http://sourceforge.net/projects/smframework/

状态模式在工作流或游戏等各种系统中有大量使用,甚至是这些系统的核心功能设计,例如政府OA中,一个批文的状态有多种:未办;正在办理;正在批示;正在审核;已经完成等各种状态,使用状态机可以封装这个状态的变化规则,从而达到扩充状态时,不必涉及到状态的使用者。

在网络游戏中,一个游戏活动存在开始;开玩;正在玩;输赢等各种状态,使用状态模式就可以实现游戏状态的总控,而游戏状态决定了游戏的各个方面,使用状态模式可以对整个游戏架构功能实现起到决定的主导作用。

状态模式实质
使用状态模式前,客户端外界需要介入改变状态,而状态改变的实现是琐碎或复杂的。

使用状态模式后,客户端外界可以直接使用事件Event实现,根本不必关心该事件导致如何状态变化,这些是由状态机等内部实现。

这是一种Event-condition-State,状态模式封装了condition-State部分。

每个状态形成一个子类,每个状态只关心它的下一个可能状态,从而无形中形成了状态转换的规则。如果新的状态加入,只涉及它的前一个状态修改和定义。

状态转换有几个方法实现:一个在每个状态实现next(),指定下一个状态;还有一种方法,设定一个StateOwner,在StateOwner设定stateEnter状态进入和stateExit状态退出行为。

状态从一个方面说明了流程,流程是随时间而改变,状态是截取流程某个时间片。





要点:

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

2,和程序状态机(PSM)不同,状态模式用类代表状态。

3,Context会将行为委托给当前的状态对象。

4,通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了。

5,状态模式和策略模式有相同的类图,但是它们的意图不同。

6,策略模式通常会用行为或算法来配置Context类。

7,状态模式允许Context随着状态的改变而改变行为。

8,状态转换可以由State类或Context类控制

9,使用状态模式通常会导致设计中类的数目大量增加

10,状态类可以被多个Context实例共享。



参考http://www.jdon.com/designpatterns/designpattern_State.htm