状态模式说明
状态模式是一种常用的设计模式,通常在一个类有多种状态,而每个状态都对应着不同的行为时,会使用到状态模式。每个状态之间是一种平行的关系,意思是,它们之间并不能相互替换,而只能在满足某些条件后切换到对应的状态。是一种被动的替换而非主动的调用。
通常在没使用状态模式时,在类中会出现非常多的if-else
的判断,导致代码的可扩展性和阅读性都比较差。而状态模式的出现,解决了多if-else
判断带来的代码不优雅的问题。
状态模式定义
当一个对象在其内部发生改变的时候改变其行为,这个类看起来就像改
变了它的类一样。
状态模式UML图
图中我们描述了三类对象,分别是Context(环境角色),IState(状态接口)和状态实现类。它们分别代表下面的意思:
- IState: 状态模式的抽象接口,它主要是将状态所对应的行为以方法的形式暴露出来,一方面是让实现类去实现,另一方面,主要是供被依赖的对象进行相应方法的调用。
- ConcreteState: 状态模式中的实现类,主要是将类的几种状态以类的形式来表现,并实现相应的方法,表明不同状态时,相同方法的不同响应。
- 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. 状态模式的优点
- 可扩展性强,如果增加了新的状态,只需要实现状态接口,对相应抽象方法进行实现即可。
- 可维护性强,将代码由多个
if-else
改变为由多个类来进行实现,代码结构更加清晰。- 低耦合,用户不需要知道具体的状态类打交道,只需要和环境角色类来进行交互即可。
2.状态模式的缺点
- 类膨胀,本来都是在一个类里处理的逻辑,要分到多个类来进行实现,类的个数急剧增加。
- 当状态接口中的方法不满足新的扩展时,需要增加或者修改相应的接口,而导致相应的实现类也会受到影响,不满足开闭原则。
3.状态模式的应用
- 有多种状态对应的不同的行为时
- 代码中出现大量与对象状态相关的条件语句时