Design Pattern学习笔记之状态机模式(State Patterns)

Design Pattern学习笔记之状态机模式(State Patterns)

1.    引子--Whois?

状态机模式用于需要根据内部状态改变行为的场景;状态机模式和策略模式类似但目标不同,策略模式使用通用接口来封装不同算法,而状态机模式使用内部状态控制自身的行为;状态的迁移可在状态类中实现也可以在context中实现,怎么选择要看设计时的考虑(允许哪部分变化);状态对象可以在多个context之间共享,但是要注意去掉当前状态。

2.    问题引入—自动售货机

这次的问题是为自动售货机设计控制程序,来看看口香糖自动售货机需要实现的功能:

熟悉状态图的同学一眼就能看出来,上面这个自动售货机要实现的功能实际上就是个状态图,它共有No Quarter,Has Quarter,Gumball Sold,Out of Gumballs四种状态,共有inserts quarter(投币),ejects quarter(退币),turns crank(转动把手),dispense gumball(吐出口香糖)四种引起状态迁移的活动。来看看代码:

public class GumballMachine {

 

    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;

    int count = 0;

 

    public GumballMachine(int count) {

       this.count = count;

       if (count > 0) {

           state = NO_QUARTER;

       }

    }

 

    public void insertQuarter() {

       if (state == HAS_QUARTER) {

           System.out.println("You can'tinsert another quarter");

       } else if (state == NO_QUARTER) {

           state = HAS_QUARTER;

           System.out.println("Youinserted a quarter");

       } else if (state == SOLD_OUT) {

           System.out.println("You can'tinsert a quarter, the machine is sold out");

       } else if (state == SOLD) {

        System.out.println("Please wait, we're already giving you agumball");

       }

    }

 

    public void ejectQuarter() {

       if (state == HAS_QUARTER) {

           System.out.println("Quarterreturned");

           state = NO_QUARTER;

       } else if (state == NO_QUARTER) {

           System.out.println("You haven'tinserted a quarter");

       } else if (state == SOLD) {

           System.out.println("Sorry, youalready turned the crank");

       } else if (state == SOLD_OUT) {

        System.out.println("You can't eject, you haven't inserted a quarteryet");

       }

    }

 

 

    public void turnCrank() {

       if (state == SOLD) {

           System.out.println("Turningtwice doesn't get you another gumball!");

       } else if (state == NO_QUARTER) {

           System.out.println("You turnedbut there's no quarter");

       } else if (state == SOLD_OUT) {

           System.out.println("You turned,but there are no gumballs");

       } else if (state == HAS_QUARTER) {

           System.out.println("Youturned...");

           state = SOLD;

           dispense();

       }

    }

 

    public void dispense() {

       if (state == SOLD) {

           System.out.println("A gumballcomes rolling out the slot");

           count = count - 1;

           if (count == 0) {

              System.out.println("Oops, outof gumballs!");

              state = SOLD_OUT;

           } else {

              state = NO_QUARTER;

           }

       } else if (state == NO_QUARTER) {

           System.out.println("You need topay first");

       } else if (state == SOLD_OUT) {

           System.out.println("No gumballdispensed");

       } else if (state == HAS_QUARTER) {

           System.out.println("No gumballdispensed");

       }

    }

 

    public void refill(int numGumBalls) {

       this.count = numGumBalls;

       state = NO_QUARTER;

    }

}

3.    变化来了—增加中奖游戏

前面的实现一直运行良好,满足了客户的需求。现在,为了刺激销售,公司计划在每个自动售货机上增加10%的中奖游戏,就是说每10次购买,就有1次中奖的机会,自动售货机会吐出两块口香糖。

显然原有的程序不能满足新的需求,我们需要进行改进,这是个好的机会,来重构我们的代码。回头再看代码,会发现原有实现在很多地方都比较拙劣:

1.      违背对功能扩展开放,对代码修改封闭的开闭准则。

2.      违背封装变化的准则,没有封装任何可能变化的部分。

3.      状态迁移出现在多个条件判断中,不够清晰。

4.      基本上不算是OO编程,更像是面向过程的编程。

我们准备重构自动售货机的控制程序,引入state类,由它来记录当前状态,管理该状态下的行为,消除复杂的条件判断语句。

第一步当然是定义state的接口(面向接口编程),创建各个状态,来看看类图:

看看代码:

public interface State {

 

    public void insertQuarter();

    public void ejectQuarter();

    public void turnCrank();

    public void dispense();

}

public class NoQuarterState implements State {

    GumballMachine gumballMachine;

 

    public NoQuarterState(GumballMachinegumballMachine) {

        this.gumballMachine = gumballMachine;

    }

 

    public void insertQuarter() {

       System.out.println("Youinserted a quarter");

       gumballMachine.setState(gumballMachine.getHasQuarterState());

    }

 

    public void ejectQuarter() {

       System.out.println("You haven'tinserted a quarter");

    }

 

    public void turnCrank() {

       System.out.println("You turned,but there's no quarter");

     }

 

    public void dispense() {

       System.out.println("You need topay first");

    }

}

public class HasQuarterState implements State {

    GumballMachine gumballMachine;

 

    public HasQuarterState(GumballMachine gumballMachine) {

       this.gumballMachine = gumballMachine;

    }

 

    public void insertQuarter() {

       System.out.println("You can'tinsert another quarter");

    }

 

    public void ejectQuarter() {

       System.out.println("Quarterreturned");

       gumballMachine.setState(gumballMachine.getNoQuarterState());

    }

 

    public void turnCrank() {

       System.out.println("Youturned...");

       gumballMachine.setState(gumballMachine.getSoldState());

    }

 

    public void dispense() {

        System.out.println("No gumball dispensed");

    }

}

public class SoldState implements State {

 

    GumballMachine gumballMachine;

 

    public SoldState(GumballMachinegumballMachine) {

        this.gumballMachine = gumballMachine;

    }

      

    public void insertQuarter() {

       System.out.println("Pleasewait, we're already giving you a gumball");

    }

 

    public void ejectQuarter() {

       System.out.println("Sorry, youalready turned the crank");

    }

 

    public void turnCrank() {

       System.out.println("Turningtwice doesn't get you another gumball!");

    }

 

    public void dispense() {

       gumballMachine.releaseBall();

       if (gumballMachine.getCount() > 0) {

           gumballMachine.setState(gumballMachine.getNoQuarterState());

       } else {

           System.out.println("Oops, outof gumballs!");

           gumballMachine.setState(gumballMachine.getSoldOutState());

       }

    }

}

public class SoldOutState implements State {

    GumballMachine gumballMachine;

 

    public SoldOutState(GumballMachinegumballMachine) {

        this.gumballMachine = gumballMachine;

    }

 

    public void insertQuarter() {

       System.out.println("You can'tinsert a quarter, the machine is sold out");

    }

 

    public void ejectQuarter() {

       System.out.println("You can'teject, you haven't inserted a quarter yet");

    }

 

    public void turnCrank() {

       System.out.println("You turned,but there are no gumballs");

    }

 

    public void dispense() {

       System.out.println("No gumballdispensed");

    }

}

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 gumballcomes 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;

    }

}

应用状态机模式改造原有实现后,有以下好处:

1.      状态和该状态下的行为被封装在对应的状态类中

2.      消除了很难理解和维护的多个条件判断语句

3.      封闭对某个状态实现逻辑的修改,方便扩展新的状态

4.      整个类图和代码和需求高度相似,拉近设计、实现和需求的距离,方便理解和维护

4.    理论篇—状态机模式定义

The StatePattern allows an object to alter its behavior when its internal state changes.The object will appear to change its class.

状态机模式允许一个对象在内部状态变化时改变行为,看起来就像是对象所属的类发生了变化一样。

定义有点抽象,怎么理解?先说第一部分,前面我们介绍过,状态机将状态下的行为封转到状态类中,以自动售货机为例,在处于NoQuarter状态下,该状态的对象允许接受顾客的Quarter;当接受了Quarter之后,状态变为HasQuarter,该状态下,不再接受顾客的Quarter,行为发生了变化;再说第二部分,从顾客的角度看,放入Quarter之前和之后,自动售货机的行为不一样,像是更改了所属的类一样,当然我们知道真相是自动售货机组合了不同的状态对象而已。

来看看类结构图:

         状态机的类图看起来跟策略模式的类图一模一样,两种模式在具体实现上很相似,但两者的目的不同。应用状态机模式,我们把状态相关的一组操作封装在状态类中,client对context的请求被一律委托给状态对象,通过组合不同的状态对象实现不同状态下的不同行为,而client对于具体的状态知之甚少。应用策略模式,一般由client确定context要组合的算法,策略模式赋予了运行期改变算法实现的能力。

一般来说,策略模式提供了一种比类继承的更灵活的方案,因为如果你使用继承来定义一个类的行为时,这个类的行为就在编译期间确定;策略模式通过组合不同的算法对象来实现不同行为,提供了运行期间改变行为的能力。

状态机模式提供了一种消除context类中复杂条件判断语句的方案,使用状态类封装状态相关的一组操作,context通过组合不同的状态对象获得行为的改变。

5.    不辨不明—没有傻问题

Q:在自动售货机的例子中,状态对象决定了状态迁移,是否在状态机模式中,都是这样?

A:不是的。有个原则是如果状态迁移是固定的,一般把迁移放到context中;如果迁移是动态变化的,倾向于把迁移放到状态对象中。由状态对象实现迁移不好的地方是会增加状态间的依赖,在自动售货机的例子中,我们使用context的get方法来获取下一个状态,从而在最大程度上减少状态间的依赖。当然,迁移可能会变化,因此把迁移放到哪里就看设计时,哪一部分允许改变。

Q:client是否会直接访问状态对象?

A:不会。状态对象是context用于表示内部状态和行为而引入的,应该由context管理这些对象。

Q:多个context是否可以共用一个状态对象?

A:当然可以。需要注意的是,共享的状态对象不能持有当前状态值,否则就需要为每一个context生成一个实例。

Q:看起来应用状态机模式增加了很多类。

A:是这样的。状态机模式把状态相关的行为封装到状态类中,肯定会增加类的数量,但是你获得了灵活性,消除了不容易理解和维护的条件判断语句。另外,系统中类的多少不是关键问题,更重要的问题是你向client暴露出的类的数量。

6.    自动售货机实现—中奖游戏

经过改造后,很方便在原有的自动售货机中扩展新的功能,来看看扩展中奖游戏后的代码。

public class WinnerState implements State {

    GumballMachine gumballMachine;

 

    public WinnerState(GumballMachinegumballMachine) {

        this.gumballMachine = gumballMachine;

    }

 

    public void insertQuarter() {

       System.out.println("Pleasewait, we're already giving you a Gumball");

    }

 

    public void ejectQuarter() {

       System.out.println("Pleasewait, we're already giving you a Gumball");

    }

 

    public void turnCrank() {

       System.out.println("Turningagain doesn't get you another gumball!");

    }

 

    public void dispense() {

       System.out.println("YOU'RE AWINNER! You get two gumballs for your quarter");

       gumballMachine.releaseBall();

       if (gumballMachine.getCount() == 0) {

           gumballMachine.setState(gumballMachine.getSoldOutState());

       } else {

           gumballMachine.releaseBall();

           if (gumballMachine.getCount() > 0) {

              gumballMachine.setState(gumballMachine.getNoQuarterState());

           } else {

              System.out.println("Oops, out of gumballs!");

              gumballMachine.setState(gumballMachine.getSoldOutState());

           }

       }

    }

}

public class GumballMachine {

 

    State soldOutState;

    State noQuarterState;

    State hasQuarterState;

    State soldState;

    State winnerState;

 

    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);

       winnerState = new WinnerState(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 gumballcomes 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 State getWinnerState() {

        return winnerState;

    }

}

7.    Review

新模式:

The StatePattern allows an object to alter its behavior when its internal state changes.The object will appear to change its class.

状态机模式允许一个对象在内部状态变化时改变行为,看起来就像是对象所属的类发生了变化一样。

OO准则:

a. 封装变化,encapsulate what varies

b. 组合优于继承, favorcomposition over inheritance

c. 面向接口编程, programto interfaces, not implementation

d. 致力于实现交互对象之间的松散耦合, strive for loosely coupled designs between objects that interact

e. 类应该对于扩展开发,对于修改封闭, classes should be open for extension but closed for modification

f. 依赖于抽象类或者接口而不是具体类。Depend on abstraction. Do not depend on concrete classes.

g. 只跟关系最密切的对象打交道。Principle of Least Knowledge – talk only to your immediate friend.

h. 别调用我,由我在需要的时候调用你。Don’t call us, we’ll call you.

i. 单一职责原则,一个类只应该因为一个理由而变化。    Single Responsibility – A class should have only one reason tochange.

 

你可能感兴趣的:(设计模式,Design,Pattern,design,pattern,head,first,状态机模式,设计模式,读书笔记)