状态模式
一、定义
状态模式( State Pattern)也称为状态机模式( State Machine pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型模式。允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类状态模式中类的行为是由状态决定的,不同的状态下有不同的行为。其意图是让一个对象在其内部改变的时候,其行为也随之改变。状态模式核心是状态与行为绑定,不同的状态对应不同的行为。
状态模式主要包含三种角色:
- 环境类角色( Context):定义客户端需要的接口,内部维护一个当前状态实例,并负责具体状态的切换
- 抽象状态角色( State):定义该状态下的行为,可以有一个或多个行为;
- 具体状态角色( Concretestate):具体实现该状态对应的行为,并且在需要的情况下进行状态切换
二、状态模式的案例
比如订单状态的变化,审核流程单子状态的变化等等。在软件开发过程中,对于某一项操作,可能存在不同的情况。通常处理方式就是使用 if.else或 switch. case条件语句进行枚举。但是这种做法天然存在弊端:条件判断语句过于臃肿,可读性差,且不具备扩展性,维护难度也大,而如果转换思维,将这些不同状态独立起来用各个不同的类进行表示,系统处于那种状态,直接使用相应的状态类对象进行处理,消除了 if. else, switch.ase等冗余语句,代码具有层次性且具备良好扩展力
状态模式主要解决的就是当控制一个对象状态的条件表达式过于复杂时得情况,通过把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简单化。对象得行为依赖于它的状态(属性),并且会根据它的状态改变而改变它的相关行为。状态模式适用于以下场景:
- 行为随状态改变而改变的场景
- 一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态
1. Context类
环境角色具有两个职责,即处理本状态必须完成的任务,及决定是否可以过渡到其它状态。对于环境角色,有几个不成文的约束:
- 即把状态对象声明为静态常量,有几个状态对象就声明几个状态常量
- 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式
public class Context { //定义状态 public final static State STATE1 = new ConcreteState1(); public final static State STATE2 = new ConcreteState2(); //当前状态 private State currentState; //获得当前状态 public State getCurrentState() { return currentState; } //设置当前状态 public void setCurrentState(State currentState) { this.currentState = currentState; // System.out.println("当前状态:" + currentState); //切换状态 this.currentState.setContext(this); } public void handle1() { this.currentState.handle1(); } public void handle2() { this.currentState.handle2(); } }
2. State抽象状态类
抽象环境中声明一个环境角色,提供各个状态类自行访问,并且提供所有状态的抽象行为,由各个实现类实现。
public abstract class State { protected Context context; public void setContext(Context context) { this.context = context; } //行为1 public abstract void handle1(); //行为2 public abstract void handle2(); }
3. 具体状态
具体状态实现,这里以定义ConcreteState1和ConcreteState2两个具体状态类为例,ConcreteState2的具体内容同ConcreteState1。
public class ConcreteState1 extends State { @Override public void handle1() { //... System.out.println("ConcreteState1 的 handle1 方法"); } @Override public void handle2() { super.context.setCurrentState(Context.STATE2); System.out.println("ConcreteState1 的 handle2 方法"); } }
public class ConcreteState2 extends State { @Override public void handle1() { //... System.out.println("ConcreteState2 的 handle2 方法"); } @Override public void handle2() { super.context.setCurrentState(Context.STATE1); System.out.println("ConcreteState2的 handle2 方法"); } }
4. Client客户端
定义Context环境角色,初始化具体状态1,执行行为观察结果。
public class Client { public static void main(String[] args) { //定义环境角色 Context context = new Context(); //初始化状态 context.setCurrentState(new ConcreteState1()); //行为执行 context.handle1(); context.handle2(); context.handle1(); context.handle2(); } }
从运行结果可见,我们已经隐藏了状态的变化过程,它的切换引起了行为的变化。对外来说,我们只看到了行为的改变,而不用知道是状态变化引起的。
三、总结
状态模式与责任链模式:
状态模式和责任链模式都能消除if分支过多的问题。但某些情况下,状态模式中的状态可以理解为责任,那么这种情况下,两种模式都可以使用从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。从其代码实现上来看,他们间最大的区别就是状态模式各个状态对象知道自己下一个要进入的状态对象;而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。
状态模式与策略模式:
状态模式和策略模式的UML类图架构几乎完全一样, 但他们的应用场景是不一样的。策略模式多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法;而状态模式各个状态间是存在相互关系的,彼此之间在一定条件下存在自动切换状态效果,且用户无法指定状态,只能设置初始状态。
优点:
结构清晰:将状态独立为类, 消除了冗余的if...else或switch...case语句, 使代码更加简洁,提高系统可维护性;
将状态转换显示化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值进行表现,不够直观;而使用状态类,在切换状态时,是以不同的类进行表示,转换目的更加明确;状态类职责明确且具备扩展性。
缺点:
类膨胀:如果一个事物具备很多状态,则会造成状态类太多;
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱;
状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需
git源码:https://gitee.com/TongHuaShuShuoWoDeJieJu/design_pattern.git