用 State 封装状态控制逻辑

第一次看到 State 设计模式第一个想到的场景就是我们工作中经常会涉及到的“状态机”,State 模式确实比较适合处理“状态机”这样的场景,它可以使不同状态的处理逻辑更内聚。

笔者最近的参与的项目中需要将门店的作业场景进行数字化,例如门店工作人员的日常拣货作业、定期库存盘点作业等。每个作业我们都会用状态来控制它的行为,例如“已领取”的作业不能被其他人领取和执行,“已关闭”的作业不能再次执行,“已完成”的作业会用于员工的薪水计算等等,所以针对作业的每个行为我们都需要进行状态的校验,并且要控制好状态流转:
image.png
这是一个非常典型的用“状态机”来控制对象行为的案例。第一版的代码是这个样子:

/**
 * 作业
 *
 * @author 0xZzzz
 * @date 2020/11/4
 */
public class Task {

    /**
     * 作业ID
     */
    private Long id;

    /**
     * 作业状态
     */
    private TaskStatus status;

    /**
     * 操作人ID
     */
    private String operatorId;

    /**
     * 实际开始时间
     */
    private Date actualStartTime;

    /**
     * 实际结束时间
     */
    private Date actualEndTime;

    /**
     * 领取作业
     */
    private void claimedBy(String operatorId) {
        if (status != TaskStatus.CREATED) {
            if (status == TaskStatus.CLOSED) {
                throw new RuntimeException("该作业已取消,不能领取!");
            }
            if (status == TaskStatus.FINISHED) {
                throw new RuntimeException("该作业已完成,不能领取!");
            }
            if (status == TaskStatus.STARTED) {
                throw new RuntimeException("该作业正在执行中,不能领取!");
            }
            if (status == TaskStatus.CLAIMED && !operatorId.equals(this.operatorId)) {
                throw new RuntimeException("该作业被其他人领取!");
            }
        }
        this.operatorId = operatorId;
        status = TaskStatus.CLAIMED;
    }

    /**
     * 开始作业
     */
    private void start(String operatorId, Date actualStartTime) {
        if (status != TaskStatus.CLAIMED) {
            if (status == TaskStatus.CLOSED) {
                throw new RuntimeException("该作业已取消,不能执行!");
            }
            if (status == TaskStatus.FINISHED) {
                throw new RuntimeException("该作业已完成,无需再次执行!");
            }
            if (status == TaskStatus.STARTED) {
                throw new RuntimeException("该作业正在执行中,无需再次执行!");
            }
            if (status == TaskStatus.CREATED) {
                throw new RuntimeException("该作业还未被认领,请领取后再执行!");
            }
        }
        if (!operatorId.equals(this.operatorId)) {
            throw new RuntimeException("您不能执行他人领取的作业!");
        }
        // 作业执行逻辑 ...
        status = TaskStatus.STARTED;
        this.actualStartTime = actualStartTime;
    }

    /**
     * 完成作业
     */
    private void finish(String operatorId, Date actualEndTime) {
        if (status != TaskStatus.STARTED) {
            if (status == TaskStatus.CLOSED) {
                throw new RuntimeException("该作业已取消,不能完成!");
            }
            if (status == TaskStatus.FINISHED) {
                throw new RuntimeException("该作业已完成!");
            }
            if (status == TaskStatus.CLAIMED) {
                throw new RuntimeException("该作业还未执行!");
            }
            if (status == TaskStatus.CREATED) {
                throw new RuntimeException("该作业还未被认领!");
            }
        }
        if (!operatorId.equals(this.operatorId)) {
            throw new RuntimeException("您不能完成他人领取的作业!");
        }
        // 作业完成逻辑 ...
        status = TaskStatus.FINISHED;
        this.actualEndTime = actualEndTime;
    }

}

我们看到,代码中充斥着各种各样的状态条件判断语句,很复杂,当我们要为 Task 扩展一个新的状态时,几乎所有的方法都要改动,要增加各种条件判断语句,这样的改动出错几率和成本都很难接受,我们可以引入 State 模式进行一次重构:
image.png
上图为 State 模式的类图展现,在门店作业的场景中,Context 即为 Task,我们为作业状态抽象一个接口 TaskState,具体的每个状态分别定义一个实现类实现这个接口,然后我们将 Task 中的 status 属性移除,增加一个 TaskState 类型的属性,Task 对象初始化时的状态为 CreatedState,后续状态的变更由 Task 的行为来触发(因为是真实的生产场景,代码做了一些删减和脱敏):

/**
 * 作业状态
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
interface TaskState {

    /**
     * 返回作业状态枚举
     *
     * @return 状态枚举
     */
    TaskStatus status();

    /**
     * 领取作业
     *
     * @param operatorId 操作人ID
     */
    void claimedBy(String operatorId);

    /**
     * 开始作业
     *
     * @param operatorId      操作人ID
     * @param actualStartTime 作业实际开始时间
     */
    void start(String operatorId, Date actualStartTime);

    /**
     * 完成作业
     *
     * @param operatorId    操作人ID
     * @param actualEndTime 作业实际完成时间
     */
    void finish(String operatorId, Date actualEndTime);
}
/**
 * 作业状态抽象类
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
abstract class AbstractTaskState implements TaskState {

    /**
     * 作业模型
     */
    protected Task task;

    AbstractTaskState(Task task) {
        this.task = task;
    }
}
import java.util.Date;

/**
 * 已创建状态
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
class CreatedState extends AbstractTaskState {

    CreatedState(Task task) {
        super(task);
    }

    @Override
    public TaskStatus status() {
        return TaskStatus.CREATED;
    }

    @Override
    public void claimedBy(String operatorId) {
        task.operatorId = operatorId;
        task.changeState(new ClaimedState(task));
    }

    @Override
    public void start(String operatorId, Date actualStartTime) {
        throw new RuntimeException("该作业批次还未被认领,请领取后再执行!");
    }

    @Override
    public void finish(String operatorId, Date actualEndTime) {
        throw new RuntimeException("该作业批次还未被认领!");
    }
}
/**
 * 已开始状态
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
class StartedState extends AbstractTaskState {

    StartedState(Task task) {
        super(task);
    }

    @Override
    public TaskStatus status() {
        return TaskStatus.STARTED;
    }

    @Override
    public void claimedBy(String operatorId) {
        throw new RuntimeException("该作业正在执行中,不能领取!");
    }

    @Override
    public void start(String operatorId, Date actualStartTime) {
        throw new RuntimeException("该作业正在执行中,无需再次执行!");
    }

    @Override
    public void finish(String operatorId, Date actualEndTime) {
        if (!operatorId.equals(task.operatorId)) {
            throw new RuntimeException("您不能完成他人领取的作业!");
        }
        task.actualEndTime = actualEndTime;
        task.changeState(new FinishedState(task));
    }

}
/**
 * 已领取状态
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
class ClaimedState extends AbstractTaskState {

    ClaimedState(Task task) {
        super(task);
    }

    @Override
    public TaskStatus status() {
        return TaskStatus.CLAIMED;
    }

    @Override
    public void claimedBy(String operatorId) {
        if (!operatorId.equals(task.operatorId)) {
            throw new RuntimeException("该作业已被其他人领取!");
        }
        throw new RuntimeException("您已领取该作业,无需再次领取!");
    }

    @Override
    public void start(String operatorId, Date actualStartTime) {
        if (!operatorId.equals(task.operatorId)) {
            throw new RuntimeException("您不能执行他人领取的作业!");
        }
        task.actualStartTime = actualStartTime;
        task.changeState(new StartedState(task));
    }

    @Override
    public void finish(String operatorId, Date actualEndTime) {
        throw new RuntimeException("该作业还未执行!");
    }

}
/**
 * 已完成状态
 *
 * @author 0xZzzz
 * @date 2020/11/6
 */
class FinishedState extends AbstractTaskState {

    FinishedState(Task task) {
        super(task);
    }

    @Override
    public TaskStatus status() {
        return TaskStatus.FINISHED;
    }

    @Override
    public void claimedBy(String operatorId) {
        throw new RuntimeException("该作业已完成,不能领取!");
    }

    @Override
    public void start(String operatorId, Date actualStartTime) {
        throw new RuntimeException("该作业已完成,无需再次执行!");
    }

    @Override
    public void finish(String operatorId, Date actualEndTime) {
        throw new RuntimeException("该作业已完成!");
    }

}
/**
 * 作业
 *
 * @author 0xZzzz
 * @date 2020/11/4
 */
@Getter
@ToString
public class Task {

    /**
     * 作业ID
     */
    private Long id;

    /**
     * 作业状态
     */
    private TaskState state;

    /**
     * 实际开始时间
     */
    Date actualStartTime;

    /**
     * 实际结束时间
     */
    Date actualEndTime;

    /**
     * 操作人ID
     */
    String operatorId;

    /**
     * 领取作业
     */
    public void claimedBy(String operatorId) {
        state.claimedBy(operatorId);
    }

    /**
     * 开始作业
     */
    public void start(String operatorId, Date actualStartTime) {
        state.start(operatorId, actualStartTime);
    }

    /**
     * 完成作业
     */
    public void finish(String operatorId, Date actualEndTime) {
        state.finish(operatorId, actualEndTime);
    }

    /**
     * 切换状态
     */
    void changeState(TaskState state) {
        this.state = state;
    }

    /**
     * 获取作业状态
     */
    public TaskStatus getStatus() {
        return state.status();
    }

}

重构过后我们将状态相关的处理逻辑分散到各个具体的 State 子类中,相对于重构前冗杂的代码结构,可读性得到了很大的提升,而且每个状态更能专注于自己的逻辑处理,不会相互影响,后续我们在改动状态处理逻辑时,改动范围也会更加集中,如果我们只扩展新的状态不扩展新的行为,可以完全满足OCP原则。State 模式有很多优点,不过我们并不建议所有的涉及到“状态机”的场景都马上用 State 模式来解决,如果你的状态处理逻辑已经足够清晰并且易于理解,那就不要强行引入 State 模式来增加设计的复杂度,灵活运用很重要~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值