设计模式之状态模式

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实例共享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值