状态模式
一. 说明
状态模式通常描述一个类不同行为的多个状态变更,对象的行为依赖它的状态,它是一种行为型模式。
状态模式可以用来消除代码中大量的if-else结构,它明确对象是有状态的、对象的不同状态对应的行为不一样、行为之间是可以切换的。简单来讲,就是对象的状态只允许在某个或某些行为下发生改变,否则不允许该行为操作对象状态。
我们用生活中购物订单的例子来说明状态模式,图示如下:
从图示中可以看出:
- 未支付的订单只能被用户做支付或者取消操作
- 已支付的订单只能被用户发货前催单 (按催单对象不同分为两种催单形式)
- 已发货的订单只能被用户发货后催单
- 已签收的订单只能被用户评价或删除
- 已删除的订单用户已经看不到了,无法做任何操作
总的来说就是用户订单的某个状态只能被用户的某个对应行为所改变,而其他行为是无法操作的。
二.应用场景
- 线上购物时的订单状态随着不同行为而变化
- 营销活动的上线审批流程,活动也是有状态变化的
- 游戏时控制的角色在不同环境下有不同的状态(buff加成或debuff时角色属性有不同的变化)
三.代码示例
以购物下单为例,我们在OrderService里实现几个行为:支付、取消订单、催单、判断是否可评价、删除订单这几个行为,按一般的写法是这样的:
先创建OrderService接口,提供几个方法
public interface OrderService {/*** 订单支付*/boolean pay(OrderDTO orderDTO);/*** 订单取消*/boolean cancel(OrderDTO orderDTO);/*** 催单*/boolean reminder(OrderDTO orderDTO);/*** 订单评价校验*/boolean isEvaluable(OrderDTO orderDTO);/*** 删除订单*/boolean delete(OrderDTO orderDTO);
}
实现Service接口,每个行为都需要判断在特定状态下才能操作
public class OrderServiceImpl implements OrderService {@Overridepublic boolean pay(OrderDTO orderDTO) {if (orderDTO.getState() == StateEnum.UNPAID) {System.out.println("pay-订单支付流程");orderDTO.setState(StateEnum.PAID);System.out.println("pay-订单支付成功");return true;}System.out.println("pay-该订单无法支付,当前状态为:" + orderDTO.getState().getName());return false;}@Overridepublic boolean cancel(OrderDTO orderDTO) {if (orderDTO.getState() == StateEnum.UNPAID) {System.out.println("cancel-订单取消流程");orderDTO.setState(StateEnum.CANCEL);System.out.println("cancel-订单取消成功");return true;}System.out.println("cancel-该订单无法取消,当前状态为:" + orderDTO.getState().getName());return false;}@Overridepublic boolean reminder(OrderDTO orderDTO) {if (orderDTO.getState() == StateEnum.PAID) {System.out.println("reminder-订单催单成功,催单对象为卖家");return true;}if (orderDTO.getState() == StateEnum.DELIVERED) {System.out.println("reminder-订单催单成功,催单对象为物流");return true;}System.out.println("reminder-该订单无法催单,当前状态为:" + orderDTO.getState().getName());return false;}@Overridepublic boolean isEvaluable(OrderDTO orderDTO) {if (orderDTO.getState() == StateEnum.SIGNED) {System.out.println("evaluable-订单可以评价");return true;}System.out.println("evaluable-该订单无法评价,当前状态为:" + orderDTO.getState().getName());return false;}@Overridepublic boolean delete(OrderDTO orderDTO) {if (orderDTO.getState() == StateEnum.SIGNED) {System.out.println("delete-订单删除流程");orderDTO.setState(StateEnum.DELETED);System.out.println("delete-订单删除成功");return true;}System.out.println("delete-订单无法删除,当前状态为:" + orderDTO.getState().getName());return false;}
}
编写测试代码
public static void main(String[] args) {OrderDTO orderDTO = new OrderDTO();orderDTO.setOrderId("202400001");orderDTO.setState(StateEnum.UNPAID);OrderService service = new OrderServiceImpl();service.pay(orderDTO);service.pay(orderDTO);service.cancel(orderDTO);service.reminder(orderDTO);orderDTO.setState(StateEnum.DELIVERED);service.reminder(orderDTO);orderDTO.setState(StateEnum.SIGNED);service.isEvaluable(orderDTO);service.delete(orderDTO);}
如上图代码所示,这些代码的特点是,OrderService下的每一方法(即行为),都需要考虑订单的各个状态,业务逻辑相对来说复杂一些,如果要增加一个新的订单状态,每个方法都要按需调整,扩展性不高,不符合开闭原则。
我们在SpringBoot环境中使用状态模式重构以上的订单流程:
首先需要引入另一组service,用来表示订单状态的一组业务,命名为OrderStateService,提供与OrderService一致的行为方法
public interface IOrderStateService {StateEnum getState();/*** 订单支付*/boolean pay(OrderDTO orderDTO);/*** 订单取消*/boolean cancel(OrderDTO orderDTO);/*** 催单*/boolean reminder(OrderDTO orderDTO);/*** 订单评价校验*/boolean isEvaluable(OrderDTO orderDTO);/*** 删除订单*/boolean delete(OrderDTO orderDTO);
}
再编写抽象业务,实现接口的方法,提供默认的实现,默认实现都是该订单状态不支持该行为操作,我们把真正的行为实现落地到具体的实现类上。
@Slf4j
public abstract class AbstractOrderStateService implements IOrderStateService {@Overridepublic boolean pay(OrderDTO orderDTO) {log.info("pay-该订单无法支付,当前状态为:{}", orderDTO.getState().getName());return false;}@Overridepublic boolean cancel(OrderDTO orderDTO) {log.info("cancel-该订单无法取消,当前状态为:{}", orderDTO.getState().getName());return false;}@Overridepublic boolean reminder(OrderDTO orderDTO) {log.info("reminder-该订单无法催单,当前状态为:{}", orderDTO.getState().getName());return false;}@Overridepublic boolean isEvaluable(OrderDTO orderDTO) {log.info("evaluable-该订单无法评价,当前状态为:{}", orderDTO.getState().getName());return false;}@Overridepublic boolean delete(OrderDTO orderDTO) {log.info("delete-该订单无法删除,当前状态为:{}", orderDTO.getState().getName());return false;}
}
再重写每个状态对应允许的行为操作,一旦重写即表示该状态允许该行为操作
//未支付状态重写支付、取消、删除方法
@Slf4j
@Service
public class UnpaidOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.UNPAID;}@Overridepublic boolean pay(OrderDTO orderDTO) {log.info("pay-订单支付成功,当前订单状态:{}", orderDTO.getState().getName());orderDTO.setState(StateEnum.PAID);return true;}@Overridepublic boolean cancel(OrderDTO orderDTO) {log.info("cancel-订单取消成功,当前订单状态:{}", orderDTO.getState().getName());orderDTO.setState(StateEnum.CANCEL);return true;}@Overridepublic boolean delete(OrderDTO orderDTO) {log.info("delete-订单取消成功,当前订单状态:{}", orderDTO.getState().getName());orderDTO.setState(StateEnum.DELETED);return true;}
}
//已支付状态重写催单方法
@Slf4j
@Service
public class PaidOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.PAID;}@Overridepublic boolean reminder(OrderDTO orderDTO) {log.info("发货前催单成功, 催单对象为卖家,当前订单状态:{}", orderDTO.getState().getName());return true;}
}
//已发货状态重写催单方法
@Slf4j
@Service
public class DeliveredOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.DELIVERED;}@Overridepublic boolean reminder(OrderDTO orderDTO) {log.info("发货后催单成功, 催单对象为物流,当前订单状态:{}", orderDTO.getState().getName());return true;}
}
//已签收状态重写判断评价和删除方法
@Slf4j
@Service
public class SignedOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.SIGNED;}@Overridepublic boolean isEvaluable(OrderDTO orderDTO) {log.info("订单允许评价,当前订单状态:{}", orderDTO.getState().getName());return true;}@Overridepublic boolean delete(OrderDTO orderDTO) {log.info("订单删除成功,当前订单状态:{}", orderDTO.getState().getName());orderDTO.setState(StateEnum.DELETED);return true;}
}
//已删除状态不做任何操作
@Service
public class DeletedOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.DELETED;}
}
//已取消状态不做任何操作
@Service
public class CancelOrderStateService extends AbstractOrderStateService {@Overridepublic StateEnum getState() {return StateEnum.CANCEL;}
}
编写获取具体StateService的工厂类
@Component
public class OrderStateFactory {private final Map<StateEnum, IOrderStateService> stateMap = new HashMap<>();@Resourceprivate Set<IOrderStateService> orderStateServiceSet;@PostConstructpublic void init() {orderStateServiceSet.forEach(service -> {stateMap.put(service.getState(), service);});}public IOrderStateService getOrderStateService(StateEnum state) {return stateMap.get(state);}}
改造OrderService的实现类
@Service
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderStateFactory orderStateFactory;@Overridepublic boolean pay(OrderDTO orderDTO) {return orderStateFactory.getOrderStateService(orderDTO.getState()).pay(orderDTO);}@Overridepublic boolean cancel(OrderDTO orderDTO) {return orderStateFactory.getOrderStateService(orderDTO.getState()).cancel(orderDTO);}@Overridepublic boolean reminder(OrderDTO orderDTO) {return orderStateFactory.getOrderStateService(orderDTO.getState()).reminder(orderDTO);}@Overridepublic boolean isEvaluable(OrderDTO orderDTO) {return orderStateFactory.getOrderStateService(orderDTO.getState()).isEvaluable(orderDTO);}@Overridepublic boolean delete(OrderDTO orderDTO) {return orderStateFactory.getOrderStateService(orderDTO.getState()).delete(orderDTO);}
}
编写测试代码
public static void test(){OrderServiceImpl service = (OrderServiceImpl) applicationContext.getBean("orderServiceImpl");OrderDTO orderDTO = new OrderDTO();orderDTO.setOrderId("202400001");orderDTO.setState(StateEnum.UNPAID);service.pay(orderDTO);service.pay(orderDTO);service.cancel(orderDTO);service.reminder(orderDTO);orderDTO.setState(StateEnum.DELIVERED);service.reminder(orderDTO);orderDTO.setState(StateEnum.SIGNED);service.isEvaluable(orderDTO);service.delete(orderDTO);}
可以看到,重构后实现了相同的业务功能,并且完全干掉了if-else的判断代码。后续新增某个订单状态,我们只需要扩展StateService的具体状态实现即可。虽然整个模块的类增加了,略显复杂,但我们牺牲复杂性去换取高可维护性和扩展性是相当值得的。
四. 总结
在状态模式中,我们把每一种状态都独立成一个类进行处理,这满足了单一职责和开闭原则。状态模式强调的是行为和状态的对应,只有在特定的业务场景下使用它,在通常三层架构的web开发中,对象的状态和对象行为一般都是分离的,所以在开发中我们使用地并不多。
状态模式和状态机也是有关系的,状态机类似一种开发引擎便于我们在开发中使用,它同样采用的是状态模式的思想实现的。