设计模式之策略模式--工作中的实际应用
策略模式,即声明一个算法(通常业务开发中就是一个处理方法),具体的实现延迟到子类(策略类)。运行时根据不同的类型选择不同的策略进行处理。侧重于扩展性,也提升了代码的可维护性。
1、背景:
公司主营港口无人驾驶业务,无人车需要接收任务才能进行动作,而任务的来源就是TOS(码头操作系统)。以目前对接的一个TOS系统来看,首先是以MQTT作为双方交互的协议,我作为消费方监听对应的TOPIC获取TOS下发的任务。它的任务类型按照orderType字段分为New(新建),Update(更新),Cancel(取消)。例如New,则说明是一条新的任务,执行新建的业务;当操作人员需要变更指令中的目的地,则使用Update进行任务更新,执行更新的业务;当任务作废时则使用Cancel取消任务,执行取消的业务。
2、分析:
2.1、策略模式引入:
根据不同的任务类型进行不同的业务处理,很明显的策略模式场景。如不使用此模式,就需要通过if-else去判断,一方面耦合度高不符合KISS原则,不利于维护。另一方面修改某一块儿业务时容易引进新的bug,不符合OCP原则。 为此定义一个接口ITosTaskHandler,声明一个抽象方法handle()由各策略子类实现。定义四个子类:
TosTaskNewHandler:新建任务业务处理
TosTaskUpdateHandler:更新任务业务处理
TosTaskCancelHandler:取消任务业务处理
DefaultTaskHandler:默认处理,定义此子类是为了防止上游增加新的类型而没有及时通知我,方便通过日志去发现
2.2、策略模式代码:
/**
* TOS任务处理接口 包含任务下发、任务更新、任务取消
*/
public interface ITosTaskHandler {
/**
* 处理TOS任务
*
* @param taskDataDTO tos任务DTO,其中orderType字段表明指令类型
*/
void handle(TaskDataDTO taskDataDTO);
}
四个子类去实现这个接口即可,特别的DefaultTaskHandler
没有具体的业务处理,只需记录日志即可。
3、使用:
接口也定义好了,实现也有了,如何使用是一个问题。最简单的可以根据orderType去判断使用具体的子类去处理,也是java多态的一个应用。但是这种方式是需要进行if-else判断,存在上述弊端 所以要换一种方式。此时问题在于如何确定使用哪个对象来处理对应的业务,四个子类都是ITosTaskHandler类型的,所以问题就简化为如何确定使用同一类型的不同对象来处理不同的业务。很明显,工厂模式 可以做到,且由于使用的是Spring框架,这个工厂直接可以使用Spring的容器。 如下是一个从Spring的容器中获取对象的类:
@Component
public class SpringBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
SpringBeanUtil.applicationContext = applicationContext;
}
protected static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(String beanName, Class<T> clazz) {
return getApplicationContext().getBean(beanName, clazz);
}
}
只需要调用getBean方法,参数传入名称和类型就能获取对应的实例对象。 此时唯一缺乏的就是bean的名称如何跟orderType进行绑定,使其在运行时可以动态选择不同的处理类进行处理。为此增加一个枚举类OrderTypeEnum,一是枚举orderType字段,二是进行orderType和beanName的绑定。代码如下:
/**
* 任务类别枚举
*/
public enum OrderTypeEnum {
/**
* 任务下发
*/
NEW("New", "tosTaskNewHandler"),
/**
* 任务更新
*/
UPDATE("Update", "tosTaskUpdateHandler"),
/**
* 任务取消
*/
CANCEL("Cancel", "tosTaskCancelHandler"),
DEFAULT("", "defaultTaskHandler");
private final String type;
private final String beanName;
OrderTypeEnum(String type, String beanName) {
this.type = type;
this.beanName = beanName;
}
public String getType() {
return type;
}
public String getBeanName() {
return beanName;
}
private static final Map<String, OrderTypeEnum> ORDER_TYPE_ENUM_MAP = Arrays.stream(values())
.collect(Collectors.toMap(OrderTypeEnum::getType, Function.identity()));
public static OrderTypeEnum fromType(String type) {
return ORDER_TYPE_ENUM_MAP.getOrDefault(type, DEFAULT);
}
}
4、落地应用:
从MQTT消息中间件中接收任务,然后根据不同的orderType使用不同的策略对象处理。
@Slf4j
@Component("tosMessageConsumerHandler")
public class TOSMessageConsumerHandler implements MessageHandler {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
log.info("接收TOS任务,message: {}", message.getPayload());
//省略前置处理以及将message.getPayLoad转换成taskDataDTO对象
/...
./
ITosTaskHandler tosTaskHandler = SpringBeanUtil.getBean(OrderTypeEnum.fromType(taskDataDTO.getOrderType()).getBeanName(), ITosTaskHandler.class);
tosTaskHandler.handle(taskDataDTO);
}
}
5、总结:
策略模式侧重于扩展性。比如将来上游新增一种业务类型时,只需要增加一个实现类实现对应的业务操作即可,不会对其它业务产生影响,满足OCP原则,具备了足够的扩展性。 亦或者将来删除某种类型,只需要删掉对应的子类即可,不影响其他业务。之后遇到需要根据xxx要做xxxxxx之类的处理的场景时,可以考虑使用策略模式。从策略模式的实际落地来看, 设计模式的使用比较灵活,不一定照搬照抄只使用一种,根据业务场景以及遇到的问题,可以结合其它设计模式一起使用,比如工厂模式。
6、思考:
上面从MQTT中消费消息,然后到后面使用策略模式具体处理中间,有一段前置处理,也是业务的一部分。这块儿有些不合理,业务操作不应该放在这里,导致业务分散且该类职责也不明确了,所以考虑TOSMessageConsumerHandler只接收消息并转换成taskDataDTO对象。然后使用模板方法设计模式,定义一个模板方法,入参是taskDataDTO,将前置固定的流程放在模板方法中处理,然后再使用策略模式...