在软件开发中,我们通常会为对象定义状态来适应不同的场景。在传统程序中,我们通常会定义状态变量来进行不同状态的切换,再通过if else语句来进行逻辑的判断,从而使不同状态的执行行为发生改变。但这样做会造成软件的臃肿和难以维护。在引入新的状态时,需要增加新的条件判断语句,这违背了“开闭原则”。本文将使用设计模式之状态模式来解决这个问题。
状态模式
状态模式的定义
状态模式(State):对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
在状态模式中,我们不将“状态”简单地定义为变量来进行处理,而是将其定义为类,用类来表示状态。这可能是个抽象的概念,下文我们将使用实例来进行详细讲解。
状态模式的结构
- 上下文/环境(Context):它维护了一个当前的状态,并提供了供开发者调用的接口,负责具体状态的切换。
- 抽象状态(State):接口,定义了上下文对象中状态对应的方法。
- 具体状态(ConcreteState):实现抽象状态接口中定义的所有方法。
状态模式的具体应用场景
- 聊天软件中的不同状态:如在线、离线、忙碌状态
- 状态机:操作系统往往需要管理不同的状态。例如安卓操作系统中就有基于状态树的决策机制。具体实现过程不是本篇的重点,感兴趣可以自行查阅操作系统的资料。
示例
本文,我们来实现一个商城订单的四种不同状态:分别是待付款,待发货,待收货和退款中状态。
不同状态有着不同的处理逻辑。
- 待付款状态下,系统会提示用户尽快付款。
- 待发货状态下,系统会提示商家尽快发货。
- 待收获状态下,系统会为用户推送物流信息。
我们使用状态模式处理这个需求。
下面我们开始编写具体代码。
首先我们先来定义一个抽象状态(State):
State.java
package com.yeliheng.state;
/**
* 抽象状态
*/
public abstract class State {
public abstract void handle(Context context);
}
在抽象状态类中,我们定义了一个抽象方法handle() 用于处理传入的上下文对象。
接着,我们可以定义一个Context类。
Context.java
package com.yeliheng.state;
public class Context {
private State state;
public Context() {
this.state = new PendingPaymentState();
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void handle() {
state.handle(this);
}
}
上下文对象中,我们定义了一个状态变量State,并提供相应的setter getter方法。方便后续具体状态设置即将切换的下一个状态。并且我们初始化初状态为PendingPaymentState即待付款状态。
我们开始定义具体的状态类。
PendingPaymentState.java
package com.yeliheng.state;
public class PendingPaymentState extends State{
@Override
public void handle(Context context) {
System.out.println("[订单待付款-买家中心] 您有一笔订单等待付款。");
context.setState(new PendingDeliveryState());
}
}
在待付款状态中,系统会输出用户提示,并将下一个状态指定给待发货状态。代码实现很简单,另外两个具体状态类同理。
PendingReceiptState.java
package com.yeliheng.state;
public class PendingReceiptState extends State{
@Override
public void handle(Context context) {
System.out.println("[订单待收货-买家中心] 卖家已发货,当前物流在:XXX省XXX市");
}
}
PendingDeliveryState.java
package com.yeliheng.state;
public class PendingDeliveryState extends State{
@Override
public void handle(Context context) {
System.out.println("[订单待发货-卖家中心] 买家已付款,请尽快发货。");
context.setState(new PendingReceiptState());
}
}
最后我们可以在main函数中调用此程序,观察输出结果。
Main.java
package com.yeliheng.state;
public class Main {
public static void main(String[] args) {
Context context = new Context(); //注册上下文
//买家付款
context.handle();
//卖家发货
context.handle();
//买家收货
context.handle();
}
}
在Main类中,我们初始化了一个上下文类,并调用了三次handle方法,分别模拟了三种订单状态。
最终程序的输出结果如下图所示:
在状态模式的应用中,我们可以发现,状态变量被不同的类代替了。并且,原先臃肿的if else逻辑被状态所替代,统一交由上下文(Context)类进行管理,程序变得更加具有条理性,这有助于项目的拓展。
状态模式的优缺点
优点
- 将状态进行封装有利于减少对象间的相互依赖。
- 状态类的职责明确,符合类的“单一职责”原则。
- 状态模式结构清晰,有利于增加项目的可拓展性,降低类之间的耦合。
缺点
- 过度使用状态模式会造成状态类的大规模增长,提升系统的维护难度。
- 状态模式有背开闭原则。增加新的状态,必须将管理状态的上下文类进行修改才能达到目的。
总结
使用状态模式能够有效减少复杂的判断逻辑,增加程序的可维护性。本例通过商城中订单状态的切换来详解状态模式。
本例的完整源代码参见:Github