【状态模式】拯救if-else堆出来的屎山代码

简介: 【状态模式】拯救if-else堆出来的屎山代码

前言

我想大家平时都在开发重都遇见过屎山代码,这些屎山代码一般都是由于复杂且庞大的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.需求

我们考虑一个简单的电梯系统,其中有以下状态:

  1. 停止状态(StoppedState): 当电梯处于停止状态时,它可以接受移动到指定楼层的请求。
  2. 上升状态(MovingState): 当电梯处于上升状态时,它不能响应移动请求,因为它正在上升。
  3. 下降状态(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);
    }
}


目录
相关文章
|
4月前
|
存储 XML JavaScript
圣诞节到了,用代码给对象写一颗圣诞树吧
JS是JavaScript的缩写,它是一种广泛使用的编程语言。JavaScript通常用于在web页面中添加动态内容、交互式特效和用户体验增强等功能。它是一种脚本语言,可以在浏览器中直接运行,也可以与服务器端进行交互。JavaScript可以用于创建复杂的应用程序,包括网页、手机应用、桌面应用以及游戏等。它具有广泛的应用领域,并且拥有大量的开发资源和社区支持。
88 3
|
数据安全/隐私保护
机房收费系统—经典代码
机房收费系统—经典代码
面试又被问懵了吗?不如把ThreadLocal拆开了揉碎看看
1.为什么用 ThreadLocal? 所谓并发,就是有限资源需要应对远超资源的访问。解决问题的方法,要么增加资源应对访问;要么增加资源的利用率。 所以,相信这年头做开发的多多少少,都会那么几个“线程二三招”、“用锁五六式”。 那所带来的就是多线程访问下的并发安全问题。 共享变量的访问域跨越了原始的单线程,进入了千家万户的线程眼里。谁都可以用,谁都可以改,那不就打起来了吗? 因此,防止并发问题的最好办法,就是不要多线程访问(这科技水平倒退二十年~)。ThreadLocal 顾名思义,将一个变量限制为“线程封闭”:对象只被一个线程持有、访问、修改。
有点迷糊的题
2541. 使数组中所有元素相等的最小操作数 II - 力扣(LeetCode)
66 0
数据结构上机实践第四周项目5 - 猴子选大王
数据结构上机实践第四周项目5 - 猴子选大王
146 0
数据结构上机实践第四周项目5 - 猴子选大王
|
存储 算法 安全
烧点脑子使劲看--对象详细讲解
当Java虚拟机遇到一条new字节码指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用所代表的类是否已经被加载,如果没有,就必须先将就该类加载到内存中,具体过程见:
92 0
|
缓存 监控 算法
啥?小胖连 JVM 对锁做了那些优化都不知道?真的菜!
JDK1.6 之前,synchronized 一直被认为是重量级锁。而在 JDK1.6 之后,JVM 对 synchronized 内置锁的性能进行了很多优化,包括「自适应的自旋锁、锁消除、锁粗化、偏向锁、轻量级锁」等等。加了这些优化之后,synchronized 锁的性能得到了大幅度的提升,下面我们来瞧瞧到底咋回事?
啥?小胖连 JVM 对锁做了那些优化都不知道?真的菜!
|
存储 人工智能 Java
肝了一整天,关于Java数组,你想知道的都在这里!
肝了一整天,关于Java数组,你想知道的都在这里!
171 0
肝了一整天,关于Java数组,你想知道的都在这里!
|
设计模式 移动开发 安全
与其硬啃“屎山”代码,不如用这六步有条不紊实现代码重构 李慧文
对大规模系统进行重构,如果一个人对着又臭又长的代码硬刚,即使花了大量的时间进行手工验证,最后仍然会有很多问题,特别是一些深路径及特殊场景下的问题。其实,大规模的系统级别重构时是有方法的。我们采访了 Thoughtworks 数字化转型与运营 资深咨询师黄俊彬(QCon+案例研习社讲师),请他来分享 MV*模式重构演进的方法和经验。
559 0
与其硬啃“屎山”代码,不如用这六步有条不紊实现代码重构 李慧文
|
Java 程序员
老爷子这代码,看跪了! (中)
老爷子这代码,看跪了! (中)
135 0
老爷子这代码,看跪了! (中)