设计模式之状态模式

图1

状态模式说明

状态模式是一种常用的设计模式,通常在一个类有多种状态,而每个状态都对应着不同的行为时,会使用到状态模式。每个状态之间是一种平行的关系,意思是,它们之间并不能相互替换,而只能在满足某些条件后切换到对应的状态。是一种被动的替换而非主动的调用。

通常在没使用状态模式时,在类中会出现非常多的if-else的判断,导致代码的可扩展性和阅读性都比较差。而状态模式的出现,解决了多if-else判断带来的代码不优雅的问题。

状态模式定义

当一个对象在其内部发生改变的时候改变其行为,这个类看起来就像改
变了它的类一样。

状态模式UML图

图1

图中我们描述了三类对象,分别是Context(环境角色),IState(状态接口)和状态实现类。它们分别代表下面的意思:

  1. IState: 状态模式的抽象接口,它主要是将状态所对应的行为以方法的形式暴露出来,一方面是让实现类去实现,另一方面,主要是供被依赖的对象进行相应方法的调用。
  2. ConcreteState: 状态模式中的实现类,主要是将类的几种状态以类的形式来表现,并实现相应的方法,表明不同状态时,相同方法的不同响应。
  3. Context: 状态模式中的环境角色,主要是暴露给用户调用的类,维护对象的状态。类似于一个代理,持有状态对象的实例,并提供状态对象所对应的相应方法供客户端调用。

状态模式的示例

这里主要是模仿电视遥控器的开关。当遥控器开了,我们可以切换相应的频道;而当遥控器关了,切换相应的频道是不起作用的。这是一个典型的状态模式的实例,根据遥控器的不同状态来作出不同的行为。

1. TVState状态模式接口类

public interface TVState {
    //开
    void turnOn();

    //关
    void turnOff();

    //上一个频道
    void preChannel();

    //下一个频道
    void nextChannel();
}

状态模式中的接口,主要提供了turnOn,turnOff,preChannel,nextChannel四个抽象方法,让实现类去根据相应的状态去进行实现。

2.具体实现类

开机的状态实现类:

public class PowerOn implements TVState {
    @Override
    public void turnOn() {

    }

    @Override
    public void turnOff() {
        System.out.println("电视机被关机了");
    }

    @Override
    public void preChannel() {
        System.out.println("上一个频道");
    }

    @Override
    public void nextChannel() {
        System.out.println("下一个频道");
    }
}

关机的状态实现类:

public class PowerOff implements TVState {
    @Override
    public void turnOn() {
        System.out.println("电视机被开机了");
    }

    @Override
    public void turnOff() {

    }

    @Override
    public void preChannel() {

    }

    @Override
    public void nextChannel() {

    }
}

这里是两个状态接口的实现类,其中,一个表示电视机开机的状态,另一个表示电视机关机的状态。其中,开机状态里,开机的行为是不会被执行的;而关机状态里,除了开机行为是可以被执行的,其它的都是不会被执行的。

3. Context(环境角色类)

public class TvContext {
    private TVState mState = new PowerOff();

    public void setState(TVState state) {
        this.mState = state;
    }

    public void turnOn() {
        mState.turnOn();
        setState(new PowerOn());
    }

    public void turnOff() {
        mState.turnOff();
        setState(new PowerOff());
    }

    public void preChannel() {
        mState.preChannel();
    }

    public void nextChannel() {
        mState.nextChannel();
    }
}

环境角色类,主要是在具体状态的实现和用户之间进行通信使用的,类似于一个代理,所以,它持有状态类实例的引用,并提供了相应状态的所有方法供用户来调用。

这里最主要的一个点就是,它维护了对象的状态,当执行了开机动作后,就将对象的状态设置成了开机状态,而执行了关机动作后,就将对象的状态设置成了关机状态。用户再接下去调用相应的方法时,就会根据当前的状态来进行响应。

4. Client

public class Client {
    public static void main(String[] args) {
        TvContext tvContext = new TvContext();
        tvContext.turnOn();
        tvContext.nextChannel();
        tvContext.preChannel();


        tvContext.turnOff();
        tvContext.nextChannel();
        tvContext.preChannel();
    }
}

客户端,首先创建了环境角色的对象,然后,开机,下一个频道,上一个频道,接下来,关机,上一个频道,下一个频道。我们来看看,其最后的结果是否符合现实生活中的状态:

电视机被开机了
下一个频道
上一个频道
电视机被关机了

当用户关机后,再调用上一个频道,下一个频道,并没有进行响应,只有开机后,才会响应下一个频道和上一个频道。与现实生活中是一致的,说明我们的代码示例没有问题。

总结

1. 状态模式的优点

  1. 可扩展性强,如果增加了新的状态,只需要实现状态接口,对相应抽象方法进行实现即可。
  2. 可维护性强,将代码由多个if-else改变为由多个类来进行实现,代码结构更加清晰。
  3. 低耦合,用户不需要知道具体的状态类打交道,只需要和环境角色类来进行交互即可。

2.状态模式的缺点

  1. 类膨胀,本来都是在一个类里处理的逻辑,要分到多个类来进行实现,类的个数急剧增加。
  2. 当状态接口中的方法不满足新的扩展时,需要增加或者修改相应的接口,而导致相应的实现类也会受到影响,不满足开闭原则。

3.状态模式的应用

  1. 有多种状态对应的不同的行为时
  2. 代码中出现大量与对象状态相关的条件语句时