状态模式(State Pattern)是一种行为型设计模式,它允许对象在其内部状态发生变化时改变其行为。状态模式的核心思想是将对象的状态封装成不同的类,并在对象内部维护一个指向当前状态的引用。这使得对象在不同状态下具有不同的行为,而且可以在运行时动态切换状态。其实这有点像策略模式,共同点都是是传入的状态|策略对象来驱动行为。下面给出例子并说明使用状态模式和不使用状态模式实现状态转换功能的区别。
状态模式实现状态转换
我们就使用电梯门的开关为例子讲解状态模式,假设我们传入一个关门的状态参数电梯门就立即关上,传入一个开门的状态参数电梯门就立即打开。那么状态模式的实现方式如下:
// 1. State 接口
interface State {
void handle(Context context);
}
// 2. 实现具体开关门状态
class OpenState implements State {
@Override
public void handle(Context context) {
System.out.println("电梯门已打开");
// 调用底层接口开门,其他处理...
}
}
class CloseState implements State {
@Override
public void handle(Context context) {
System.out.println("电梯门已关闭");
//调用底层接口关门, 其他处理...
}
}
// 3. Context 状态上下文,维护状态引用
// 作用就是接受状态参数切换状态,并可以调用该状态下的行为
class Context {
private State currentState;
public void setState(State state) {
this.currentState = state;
}
public void request() {
currentState.handle(this);
}
}
public class StateModelDemo {
// 4. 测试状态切换
public static void main(String[] args) {
Context context = new Context();
State openState = new OpenState();
State closeState = new CloseState();
context.setState(openState);
context.request(); // 输出:电梯门已打开
context.setState(closeState);
context.request(); // 输出:电梯门已关闭
}
}
物流状态切换例子
假设有个需求是有个物流状态的切换,状态分为四种分别是:未收件、已收件、运输中、已签收四种状态,并且状态是单向无环不可逆的。我们使用状态模式和不使用状态模式的区别就是不用写大量的 if else 循环,下面演示两种实现方式:
传统写法
传统写法很简单,但是每到进行状态转换的地方就需要做无数的判断,如下所示:
class Order{
private Long id;
private String state;
public Order(){
this.state = "UNRECEIVED";
}
public void setState(String state){
this.state = state;
}
public String getState(){
return this.state;
}
}
public class OneWayState {
public static void main(String[] args) {
Order order = new Order();
// 获取当前订单状态
String state = order.getState();
System.out.println(order.getState());
// 订单状态转换
if (state.equals("UNRECEIVED")) {
order.setState("RECEIVED");
} else if (state.equals("RECEIVED")) {
order.setState("TRANSPORTING");
} else if (state.equals("TRANSPORTING")) {
order.setState("FINISHED");
} else if (state.equals("FINISHED")) {
order.setState(null);
}
System.out.println(order.getState());
}
}
使用状态模式状态转换的代码优化
首先我们定义一个订单状态的接口,将状态抽离出来,如下:
public interface OrderState {
public void nextState(Order order);
}
接着我们就要实现四种不同的订单状态,我们需要写四个状态实现类实现上面的OrderState
接口,这样太烦了,不如直接定义成一个枚举类实现OrderState
接口,如下所示,在枚举类中我们定义了上述接口的实现方法:
public enum OrderStateEnum implements OrderState{
// 状态分为四种分别是:未收件、已收件、运输中、已签收
// 未收件
UNRECEIVED{
@Override
public void nextState(Order order) {
order.setState(RECEIVED);
}
},
// 已收件
RECEIVED{
@Override
public void nextState(Order order) {
order.setState(TRANSPORTING);
}
},
// 运输中
TRANSPORTING{
@Override
public void nextState(Order order) {
order.setState(FINISHED);
}
},
// 已签收
FINISHED{
@Override
public void nextState(Order order) {
order.setState(null);
}
},
}
最后编写我们的订单类Order
类,如下:
public class Order {
private Long id;
private OrderStateEnum state;
public Order(){
this.state = OrderStateEnum.UNRECEIVED;
}
public void nextState(){
this.state.nextState(this);
}
public void setState(OrderStateEnum state){
this.state = state;
}
public OrderStateEnum getState(){
return this.state;
}
}
最后在测试类中我们可以通过简单调用nextState()
即可进行状态转换,如下所示:
public class Main {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.getState());
order.nextState();
System.out.println(order.getState());
order.nextState();
System.out.println(order.getState());
order.nextState();
System.out.println(order.getState());
}
}
打印结果如下:
UNRECEIVED
RECEIVED
TRANSPORTING
FINISHED
以后在所有的业务中,你仅仅需要调用order.getState()
即可将当前的订单状态切换到下一个状态,避免每一次都写大量的 if else
等逻辑,代码非常具有可维护性,如果以后要新加状态或者改状态名,只需要在状态枚举类中修改即可,改一处即可,如果if else
的方法则需要到处修改。这就是设计模式的魅力!!!!! 设计模式YYDS!听懂,掌声!
如果具有分叉或者环的状态如何实现?
如果是类似下面线程生命周期的状态,具有环和分叉的状态转换,如何使用状态设计模式实现呢?其实都是一样的,只不过判断条件多一点而已。在枚举实现类中的不是简单地调用nextState()
方法,而是需要传入一个参数表示方向,从而判断状态的变化。
如下仅给出运行状态的部分判断逻辑的实现,如果逻辑较为复杂可以不使用枚举类:
//具体状态类:运行状态
class Running extends ThreadState {
public Running() {
stateName = "运行状态";
System.out.println("当前线程处于:运行状态.");
}
public void suspend(ThreadContext hj) {
System.out.print("调用suspend()方法-->");
if (stateName.equals("运行状态")) {
hj.setState(new Blocked());
} else {
System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
}
}
public void stop(ThreadContext hj) {
System.out.print("调用stop()方法-->");
if (stateName.equals("运行状态")) {
hj.setState(new Dead());
} else {
System.out.println("当前线程不是运行状态,不能调用stop()方法.");
}
}
}