前言
我想大家平时都在开发重都遇见过屎山代码,这些屎山代码一般都是由于复杂且庞大的if-else造成的,状态模式,是一种很好的优化屎山代码的设计模式,本文将采用两个业务场景的示例来讲解如何使用状态模式拯救屎山代码。
1.网购业务场景
1.1.需求
我们来假设一个网购的业务场景,需求如下:
- 流程为付款、再发货、在收货,流程必须按照以上顺序,也就是说发货后不能支付、收货后不能发货和支付
- 付款后不能重复付款、发货后不能重复发货、收货后不能重复收货
1.2.if else的实现
这里我们设计一个Order订单类,用int型的state来表示状态,当然也可以用一个枚举类来表示状态会更规范一点,这里为了方便而已。
public class Order { //1 未付款 //2 已付款 //3 未发货 //4 已发货 //5 未收货 //6 已收货 private int state; public int getState() { return state; } public void setState(int state) { this.state = state; } }
以收货方法为例,业务逻辑实现出来会是:
public void receive(Order order){ if(order.getState()==2){ if(order.getState()==4){ if(order.getState()==5){ System.out.println("收货成功"); }else{ System.out.println("已收货,无法重复收货"); } }else{ System.out.println("未发货"); } }else{ System.out.println("未付款"); } }
可以看到一座小屎山代码已经初具规模,但凡状态再多一点、业务逻辑再复杂一点,这座屎山将会基本不具备可读性。
1.3.状态模式的实现
其实仔细观察可以发现,很多时候状态往往是和实体的行为是相关的。之所以引入状态,我们是希望实体在不同的状态时呈现出不同的行为。
以上面的场景为例,在支付状态下,我们希望实体能呈现出支付相关的能力;在发货状态下呈现出发货相关的能力;在收货状态下呈现出收货相关的能力......
所以完全可以把状态和能力封装在一起,从而省掉外界的if-else判断,这就是所谓的状态模式。
状态模式总结起来一句话:
实体在不同的状态,拥有不同的行为。
作用是:
可以省掉大量判断条件带来的if-else逻辑分支,使得代码更简洁易读。
接下来我们用状态模式去改写之前的代码。
首先总结一下实体类会有的行为有哪些,其实就是付款、发货、收货,也就是三个方法,为了代码的规范,可以抽象出行为接口,当然不抽象也可以,仁者见仁智者见智。
public interface OrderState{ void pay(Order order); void ship(Order order); void receive(Order order); }
接下来总结一下系统里面的状态,订单有三个维度的六种状态,分别是:
- 付款状态
- 未付款
- 已付款
- 发货状态
- 未发货
- 已发货
- 收货状态
- 未收货
- 已收货
于是可以得到有三个状态实体。
将状态和行为绑定,可以得到以下三个状态实体。
支付状态实体:
public class PayState implements OrderState{ public void pay(Order order) { System.out.println("已支付,不能再次支付!"); } public void ship(Order order) { order.setOrderState(new ShipState()); System.out.println("已发货!"); } public void receive(Order order) { System.out.println("未发货!不能收货!"); } }
发货状态实体:
public class ShipState implements OrderState{ public void pay(Order order) { System.out.println("已发货!禁止重复支付!"); } public void ship(Order order) { System.out.println("已经发货!禁止重复支付"); } public void receive(Order order) { order.setOrderState(new ReceiveState()); System.out.println("收货成功!"); } }
收货状态实体
public class ReceiveState implements OrderState{ public void pay(Order order) { System.out.println("已收货,不能再次支付!"); } public void ship(Order order) { System.out.println("已收货,不能再次发货!"); } public void receive(Order order) { System.out.println("已收货,不能再次收货!"); } }
测试代码:
public class Test { public static void main(String[] args) { Order order=new Order(); //初始状态未待支付 order.setOrderState(new PayState()); order.pay(); order.ship(); order.receive(); } }
测试结果:
2.电梯业务场景
2.1.需求
我们考虑一个简单的电梯系统,其中有以下状态:
- 停止状态(StoppedState): 当电梯处于停止状态时,它可以接受移动到指定楼层的请求。
- 上升状态(MovingState): 当电梯处于上升状态时,它不能响应移动请求,因为它正在上升。
- 下降状态(MovingState): 当电梯处于下降状态时,它也不能响应移动请求,因为它正在下降。
规则:
- 当电梯处于停止状态时,它可以接受移动到指定楼层的请求,并切换到移动状态(上升或下降)。
- 当电梯处于上升状态或下降状态时,它不能接受移动请求,而是提示当前正在移动。
- 电梯在移动过程中不能响应其他移动请求,直到它到达指定楼层并切换到停止状态。
在上面的业务情景中,我们通过使用状态模式对电梯系统进行了优化。每个状态(停止状态和移动状态)都对应一个状态类,并定义了在该状态下的行为。电梯状态的切换由上下文类
(ElevatorStateContext)来管理,它负责在不同状态下执行不同的行为,并根据状态的变化进行切换。通过使用状态模式,我们将状态切换逻辑封装到不同的状态类中,使代码更加模块化和可扩展。
2.2.if else的实现
class ElevatorIfElse { private String state = "停止"; private int currentFloor = 1; public void setState(String newState) { state = newState; } public void moveToFloor(int floor) { if (state.equals("停止")) { System.out.println("电梯从 " + currentFloor + " 楼移动到 " + floor + " 楼"); currentFloor = floor; } else if (state.equals("上升")) { System.out.println("电梯正在上升,不能移动"); } else if (state.equals("下降")) { System.out.println("电梯正在下降,不能移动"); } } } public class MainIfElse { public static void main(String[] args) { ElevatorIfElse elevator = new ElevatorIfElse(); elevator.moveToFloor(5); elevator.setState("上升"); elevator.moveToFloor(3); elevator.moveToFloor(7); elevator.setState("停止"); elevator.moveToFloor(2); } }
2.3.状态模式的实现
interface ElevatorState { void moveToFloor(ElevatorStateContext context, int floor); } class StoppedState implements ElevatorState { @Override public void moveToFloor(ElevatorStateContext context, int floor) { System.out.println("电梯从 " + context.getCurrentFloor() + " 楼移动到 " + floor + " 楼"); context.setCurrentFloor(floor); context.setState(new MovingState()); } } class MovingState implements ElevatorState { @Override public void moveToFloor(ElevatorStateContext context, int floor) { System.out.println("电梯正在移动,不能移动"); } } class ElevatorStateContext { private ElevatorState state; private int currentFloor = 1; public ElevatorStateContext() { this.state = new StoppedState(); } public void setState(ElevatorState state) { this.state = state; } public void moveToFloor(int floor) { state.moveToFloor(this, floor); } public int getCurrentFloor() { return currentFloor; } public void setCurrentFloor(int currentFloor) { this.currentFloor = currentFloor; } } public class MainStatePattern { public static void main(String[] args) { ElevatorStateContext context = new ElevatorStateContext(); context.moveToFloor(5); context.setState(new MovingState()); context.moveToFloor(3); context.moveToFloor(7); context.setState(new StoppedState()); context.moveToFloor(2); } }