状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。
介绍
意图
允许一个对象在其内部状态改变时改变其行为,看起来就像是改变了其类一样。
主要解决的问题
- 状态模式解决对象行为依赖于其状态的问题,使得对象可以在状态变化时切换行为。
使用场景
- 当代码中存在大量条件语句,且这些条件语句依赖于对象的状态时。
实现方式
- 定义状态接口:声明一个或多个方法,用于封装具体状态的行为。
- 创建具体状态类:实现状态接口,根据状态的不同实现具体的行为。
- 定义上下文类:包含一个状态对象的引用,并在状态改变时更新其行为。
关键代码
- 状态接口:声明行为方法。
- 具体状态类:实现状态接口,封装具体行为。
- 上下文类:维护一个状态对象,并提供方法以改变其状态。
应用实例
- 篮球运动员状态:运动员可以有正常、不正常和超常等状态。
- 曾侯乙编钟:编钟作为上下文,不同的钟(状态)有不同的演奏效果。
优点
- 封装状态转换规则:将状态转换逻辑封装在状态对象内部。
- 易于扩展:增加新的状态类不会影响现有代码。
- 集中状态相关行为:将所有与特定状态相关的行为集中到一个类中。
- 简化条件语句:避免使用大量的条件语句来切换行为。
- 状态共享:允许多个上下文对象共享同一个状态对象。
缺点
- 增加类和对象数量:每个状态都需要一个具体的状态类。
- 实现复杂:模式结构和实现相对复杂。
- 开闭原则支持不足:增加新状态或修改状态行为可能需要修改现有代码。
使用建议
- 当对象的行为随状态改变而变化时,考虑使用状态模式。
- 状态模式适用于替代复杂的条件或分支语句。
注意事项
- 状态模式适用于状态数量有限(通常不超过5个)的情况。
- 谨慎使用,以避免系统变得过于复杂。
结构
状态模式包含以下几个主要角色:
上下文(Context):定义了客户感兴趣的接口,并维护一个当前状态对象的引用。上下文可以通过状态对象来委托处理状态相关的行为。
状态(State):定义了一个接口,用于封装与上下文相关的一个状态的行为。
具体状态(Concrete State):实现了状态接口,负责处理与该状态相关的行为。具体状态对象通常会在内部维护一个对上下文对象的引用,以便根据不同的条件切换到不同的状态。
实现
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。
StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
步骤 1
创建一个接口。
State.java
public interface State {
public void doAction(Context context);
}
步骤 2
创建实现接口的实体类。
StartState.java
public class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
StopState.java
public class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
步骤 3
创建 Context 类。
Context.java
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
步骤 4
使用 Context 来查看当状态 State 改变时的行为变化。
StatePatternDemo.java
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
步骤 5
执行程序,输出结果:
Player is in start state Start State Player is in stop state Stop State
匿名者
243***0082@qq.com
状态模式有些问题,应当如下设计:
State接口如下:
StartState 类设计如下:
CloseState 类:
Context 类:
StatePatternDemo 设计如下:
匿名者
243***0082@qq.com
醉梦先森
197***8624@qq.com
上述笔记代码有点问题,开机状态和关机状态需要修改成下面这样:
Context类也要修改:
醉梦先森
197***8624@qq.com
Johnny
yhr***@163.com
上述代码可以优化一下,增加了判断,已经 start 的需要设置状态为 start。
Context.java 如下:
Johnny
yhr***@163.com
Siskin.xu
sis***@sohu.com
Python 方式:
Siskin.xu
sis***@sohu.com
YesOk
862***656@qq.com
第一个笔记这样改写,用的是策略模式。不是状态模式,对象的行为依赖于它的状态(属性),随状态改变而改变它的相关行为,看 demo 例子:
Start或Stop的状态改变,context打印出的状态,是随状态改变而变的。实际应用场景,只需要判断context的状态,业务逻辑做相应变化。
第二个笔记,每个状态都独立的,反而将状态之间耦合在一起,改成例子那样,多好:
以上个人观点,欢迎交流学习。
YesOk
862***656@qq.com
养吾剑
979***716@qq.com
这个完全误人子弟了。
笔记二是对的,最后一个笔记yesok的再次误人子弟。
把context换成computer就明白了,computer如果只有开机,关机,播放三种操作,但是有开机,关机,播放中三种状态。每种状态能够执行的操作肯定是不同的。
开机 关机 播放
开机中 不可 可 可
关机中 可 不可 不可
播放中 不可 可 不可
状态即使抽象出来,也只是computer的属性,方便扩展不写switch而已,状态的变化肯定是源于computer自身的动作的,用oo的思想来看,调用者对于状态state的api应该是不可见的。
new computer时,会设置一个默认状态,我们的例子应该是关机中。然后关机中的computer只能有一个有效操作,就是开机,至于其他两种操作是设置为无响应,还是log提示无法操作,那也随便了。
最后,状态切换本身就是每个状态的内禀属性,关机中通过开机操作切换到开机中的代码,应该写在CloseState里,至于说耦合问题,这些状态本身互相之间就是有联系的,怎么可能互相独立?
养吾剑
979***716@qq.com
hahand
hah***@yeah.net
说此文误人子弟那个,状态之间必须解耦,难道我增删改其中一个状态还需要把多个状态也一起修改了不成?
伪代码如下:
状态模式只有在复杂业务情况下才需要单独封装状态类,一般情况下走枚举便可。
状态对象对于业务逻辑的描述,使用方法而不是变量更优。
hahand
hah***@yeah.net
伴鹰之风
ppa***0523ecb8c9@sohu.com
状态之间不是解耦的,所以才说状态模式对“开闭原则”的支持并不好。
状态模式中类有状态,状态的修改会改变整个类行为。
策略没有状态,策略的选择由客户端决定。
运行结果:
伴鹰之风
ppa***0523ecb8c9@sohu.com