这篇博客我们来介绍一下状态模式(State Pattern),也是行为型设计模式之一。状态模式的行为是由状态来决定的,不同的状态下有不同的行为。状态模式和策略模式的结构类图几乎完全一样,但它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类;而策略模式可以想象成是除了继承之外的一种弹性替代方案,如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难,有了策略模式,你可以通过组合不同的对象来改变行为。状态模式的意图是让一个对象在其内部状态发生改变的时候,其行为也随之改变。
设计模式总目录
特点
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式的使用场景:
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为;
- 代码中包含大量与状态有关的条件语句,例如,一个操作中含有庞大的多分枝语句(if-else 或者 switch-case),且这些分支依赖于该对象的状态。
状态模式将每一个条件分支放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化,这样通过多态来去除过多的、重复的 if-else 等分支语句。
UML类图
状态模式的 uml 类图有三个角色:
- Context:环境类,定义客户感兴趣的接口,维护一个 State 子类,这个实例定义了对象的当前状态;
- State:抽象状态类或者状态接口,定义一个或者一组接口,表示该状态下的行为;
- ConcreteStateA、ConcreteStateB:具体状态类,每一个具体的状态类实现抽象的 State 中定义的接口,从而达到不同状态下的不同行为。
据此我们可以写出状态模式的通用代码:
状态接口以及相关子类:
State.class
public interface State {
void doSomething();
}
ConcreteStateA.class、ConcreteStateB.class、NullState.class
public class ConcreteStateA implements State {
@Override
public void doSomething() {
System.out.print("this is ConcreteStateA's function\n");
}
}
public class ConcreteStateB implements State{
@Override
public void doSomething() {
System.out.print("this is ConcreteStateB's function\n");
}
}
public class NullState implements State{
@Override
public void doSomething() {
//do nothing
}
}
Context类以及测试代码:
Context.class
public class Context {
private State state = new NullState();
void setState(State state) {
this.state = state;
}
void doSomething() {
state.doSomething();
}
public static void main(String[] args) {
Context context = new Context();
context.setState(new ConcreteStateA());
context.doSomething();
context.setState(new ConcreteStateB());
context.doSomething();
}
}
最后结果:
this is ConcreteStateA's function
this is ConcreteStateB's function
示例与源码
在 Android 源码中也有很多状态模式的例子,MediaPlayer 和 WifiStateEngine 等,这里我们来简单看看 MediaPlayer 的状态机:
椭圆代表 MediaPlayer 对象可能驻留的状态。弧线表示驱动 MediaPlayer 在各个状态之间迁移的播放控制操作。这里有两种类型的弧线。由一个箭头开始的弧代表同步的方法调用,而以双箭头开头的代表的弧线代表异步方法调用。MediaPlayer在这我就不详细介绍了,网上资料很多,感兴趣的可以去查阅一下。
这里再介绍一下状态机,又称为有限状态自动机 (FSM:Finite State Machine),是表示有限多个状态以及在这些状态之间转移和动作的数学模型。状态存储关于过去的信息,它反映从系统开始到现在时刻输入的变化;转移指示状态变更,用必须满足来确使转移发生的条件来描述它;动作是在给定时刻要进行的活动描述,详细的看看这篇博客:有限状态机(FSM)的Java 演示 。
实际 android 开发过程中,我们一般会去根据实际情况去先构造一个状态图,定义每个状态和每个状态之间切换的事件,类似于上图的 MediaPlayer,然后将该信息录入进入状态机,当目前的状态接收到一个非法的跳转事件时,可以抛出异常,这样就能保证一切按照预先设定好的方向进行。
Demo
我们这里仍然以 wiki 上的 demo 为例,来实现一堆字符串的一个大小写间隔打印:
Statelike.class
interface Statelike {
void writeName(StateContext context, String name);
}
StateLowerCase.class 和 StateMultipleUpperCase.class
class StateLowerCase implements Statelike {
@Override
public void writeName(final StateContext context, final String name) {
System.out.println(name.toLowerCase());
context.setState(new StateMultipleUpperCase());
}
}
class StateMultipleUpperCase implements Statelike {
/** Counter local to this state */
private int count = 0;
@Override
public void writeName(final StateContext context, final String name) {
System.out.println(name.toUpperCase());
/* Change state after StateMultipleUpperCase's writeName() gets invoked twice */
if(++count > 1) {
context.setState(new StateLowerCase());
}
}
}
StateContext.class
class StateContext {
private Statelike myState;
StateContext() {
setState(new StateLowerCase());
}
/**
* Setter method for the state.
* Normally only called by classes implementing the State interface.
* @param newState the new state of this context
*/
void setState(final Statelike newState) {
myState = newState;
}
public void writeName(final String name) {
myState.writeName(this, name);
}
public static void main(String[] args) {
final StateContext sc = new StateContext();
sc.writeName("Monday");
sc.writeName("Tuesday");
sc.writeName("Wednesday");
sc.writeName("Thursday");
sc.writeName("Friday");
sc.writeName("Saturday");
sc.writeName("Sunday");
}
}
最后结果:
monday
TUESDAY
WEDNESDAY
thursday
FRIDAY
SATURDAY
sunday
例子也很简单,一目了然。
总结
状态模式的关键点在于不同的状态下对于统一行为有不同的响应,这其实就是一个将 if-else 用多态来实现的一个具体实例。在 if-else 或者 switch-case 形势下根据不同的状态进行判断,如果是状态 A 那么执行方法 A,状态 B 执行方法 B,但这种实现使得逻辑耦合在一起,易于出错不易维护,通过状态模式能够很好的消除这类“丑陋”的逻辑处理,当然并不是任何出现 if-else 的地方都应该通过状态模式重构,模式的运用一定要考虑所处的情景以及你要解决的问题,只有符合特定的场景才建议使用对应的模式。和程序状态机(PSM)不同,状态模式用类代表状态,状态的转换可以由 State 类或者 Context 类控制。
优点:
- 通过将每个状态封装进一个类,将以后所做的修改局部化;
- 将所有与一个特定状态相关的行为封装到一个对象中,繁琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时增加可维护性和可扩展性。
缺点当然也很明显,也是绝大部分设计模式的通病,类数目的增多。
源码下载
https://github.com/Jakey-jp/Design-Patterns/tree/master/StatePattern
引用
https://en.wikipedia.org/wiki/State_pattern
http://blog.csdn.net/jason0539/article/details/45021055
http://blog.csdn.net/shulianghan/article/details/38487967
http://blog.csdn.net/eager7/article/details/8517827