设计模式之状态模式
小编已经很长时间没有来写文章,刚好最近在看设计模式。为了督促自己坚持学习下去,小编把今天认真学习状态模式,并结合在实际项目中的应用来说说心得体会。有不足或者需要纠正之处,欢迎读者留言指正,我会虚心学习
不多说了,直接切入正题
相信在项目中大家也都遇到过与订单相关的业务,在业务中订单状态的切换会直接或者间接引入第三方业务处理。有人可能使用了switch分支处理,导致整个类方法很大,或者引入的业务越来越复杂,致使代码耦合度很高;也有人可能使用了if语句判断,导致代码读取很不方便。那么今天小编结合在项目中的使用就介绍下23中设计模式之状态模式。
这里我们引入一个一张结构图直观说明
状态模式包含以下主要角色。
环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
简单了解了之后 我们来使用设计模式-简单工厂模式定义接口并实现
1.定义接口 订单状态服务
/**
* 使用状态模式控制 不同状态业务流转
* @author lgl
* @date 2021/8/31
*/
public interface OrderStateService {
/**
* 状态不同返回结果可能不同
* @return
*/
Object orderService();
/**
* 类标识
* @return
*/
String stateFlag();
}
2.定义切换签收-状态切换实现类
/**
* 签收逻辑处理
* 比如:签收后需要 结束订单 消息通知收货人和发货人
* 可能还会涉及到触发资金结算问题
* @author lgl
* @date 2021/8/31
*/
@Slf4j
@Service
public class OrderSignedStateServiceImpl implements OrderStateService {
@PostConstruct
@Override
public String stateFlag() {
SpringUtil.STATE_MAP.put("signed","alreadySignedOrderStateServiceImpl");
return "signed";
}
@Override
public Object orderService() {
log.info("状态已经切换到已签收");
return "signed";
}
}
3.定义发货-状态实现类
/**
* 商家发货后
* 如果用户开通了消息提醒功能 消息通知用户商家已发货以及货品定位信息
* @author lgl
* @date 2021/8/31
*/
@Slf4j
@Service
public class OrderShipStateServiceImpl implements OrderStateService {
@PostConstruct
@Override
public String stateFlag() {
SpringUtil.STATE_MAP.put("Shipped","shippedAlreadyOrderStateServiceImpl");
return "Shipped";
}
/**
@Override
public Object orderService() {
log.info("状态已经切换到配送中");
return "shipped";
}
}
这里小编就定义两个实现类吧,其中实现类这种 stateFlag() 需要项目启动加载,我使用适用了**@PostConstruct** 注解来解决启动后加载。当前读者也可以使用让实现类实现 InitializingBean类中接口afterPropertiesSet() 来替换小编的stateFlag() ,毕竟条条大路通罗马。
/**
* @author lgl
* @date 2021/8/31
*/
@Slf4j
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
/**
* 状态与类名的映射
*/
public static Map<String,String> STATE_MAP = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.context = applicationContext;
}
/**
* 获取上下文对象
* @return
*/
public static ApplicationContext getContext(){
return context;
}
/**
* 根据name获取对象bean
* @param name
* @return
*/
public static Object getBean(String name){
return getContext().getBean(name);
}
/**
* 根据class获取对象bean
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz){
return getContext().getBean(clazz);
}
/**
* 根据name 以及对象指定返回bean
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name,Class<T> clazz){
//改造前: name是之具体的类名
//改造后:name为状态映射类名 这样前端直接传具体的状体就可以了 也可以把该参数维护成枚举 这样就省去了接口中的stateFlag()方法的的标记
String clsName = STATE_MAP.get(name);
return getContext().getBean(clsName,clazz);
}
}
读者也可以使用hutool工具包中的SpringUtil来通过上下文获取bean实例对象,这里不做赘述
4.自定义环境上下文
/**
* 方式一:
* 通过注入到组件中调用状态流转事件
* @author lgl
* @date 2021/8/31
*/
public class StateContext {
private OrderStateService orderStateService;
public StateContext(OrderStateService orderStateService){
this.orderStateService = orderStateService;
}
/**
* 入参订单号
* @param orderNo
*/
public void switchState(String orderNo){
orderStateService.orderService();
}
}
5.通过postman调用接口
**
*
* 状态模式 其实类似于 switch 分支处理的做法
* 在业务处理时 通过上下文直接获取bean对象 通过bean对象操作业务数据
*
* 原生状态模式需改造成符合当前业务
* @author lgl
* @date 2021/8/31
*/
@RestController
@RequestMapping("order")
public class OrderStateController {
@GetMapping("state")
public String orderState(String stateBeanId){
// 通过spring上下文获取bean对象stateBeanId为类名称 也可以不做封装 直接使用 hutool 中的 SpringUtil
OrderStateService state = SpringUtil.getBean(stateBeanId, OrderStateService.class);
StateContext stateContext = new StateContext(state);
stateContext.switchState(stateBeanId);
return "OK";
}
}
为了方便小编把逻辑代码卸载了控制层,实际项目中控制层制作转发调用业务层。这里不做具体的postman调用结果。读者可自行调试运行
在整个过程中,小编没有完全使用原生的设计模式,通过改造注入小编的部分思想。因为我一直认为:
读书百遍其义自见
接下来小编会把模板模式结合策略模式在支付领域的实际应用写一篇文章