一、背景
在软件开发中常常遇到这种情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。策略模式(Strategy Pattern)定义了一组策略,分别在不同类中封装起来,每种策略都可以根据当前场景相互替换,从而使策略的变化可以独立于操作者。比如我们去某个地方,会根据目标地的距离或者手头经济情况,来选择不同的出行方式(共享单车、公交、滴滴、高铁、飞机等),这些出行方式即是不同的策略。
二、使用时机
了解完背景我们对策略模式心里有个底了,核心是把不同的策略封装起来,在不同的场景把该策略拎出来。额,这咋一看不就是if...else...的逻辑么。既然这样,我那我们就来看下我们最初学代码的那个场景。
if (conditionA) { logicA } else if (conditionB) { logicB } else if (conditionC) { logicC } else { logicD }
当你开始学的时候,问题不大,能跑就行。当你有了一定经验以后,这段代码明显违反了OOP的两个基本原则:
- 单一职责原则(SPR,Single Responsibility Principle):一个类或者模块只负责完成一个职责或者功能。
- 开闭原则(OCP,Open Closed Principle):软件实体(模块、类、方法等)应该”对扩展开放、对修改关闭“。
因为违反了这两个原则,当if-else块中的代码量比较大时,后续的代码会变得越来越难以维护,而且不小心就改出问题,到时候等着背锅吧。当然你会问,老周,那我咋样才能避免写这样的代码啊!额,根据老周的经验,当你就两层左右,可以这样写,不要做过度设计;三层以上,但每层代码行比较少的话,可以使用卫语句;当三层以上并且每层代码量比较多时,则需要使用策略模式了。
三、最佳实践
1、需求
比如我们模型训练平台,之前只是模型输入的提交,后面业务方还希望支持在线预览表单提交以及绑定dubbo服务提交给其它平台。
这样的话咱们的表单提交就要响应业务方支持下面三种提交类型:
- 模型输入的提交
- 在线预览表单提交
- 绑定dubbo服务提交
铁子们,现在我们就来最佳实践一波~
2、定义策略接口
- 获取策略类型
- 处理策略逻辑
/** * 表单提交处理器 * @param <R> 业务值 */ public interface FormSubmitHandler<R extends Serializable> { /** * 获得提交类型 * @return 提交类型 */ String getSubmitType(); /** * 处理表单提交请求 * @param request 请求 * @return 响应,left:为返回给前端的提示信息,right:为业务值 */ CommonPairResponse<String, R> handleSubmit(FormSubmitRequest request); }
/** * 表单提交的请求 */ @Getter @Setter public class FormSubmitRequest { /** * 提交类型 */ private String submitType; /** * 用户id */ private Long userId; /** * 表单提交的数据 */ private Map<String, Object> formInput; }
其中,FormSubmitHandler 的 getSubmitType 方法用来获取表单的提交类型(即策略类型),用于根据客户端传递的参数直接获取到对应的策略实现;客户端传递的相关参数都被封装为 FormSubmitRequest,传递给 handleSubmit 进行处理。
3、相关策略实现
模型输入的提交:
@Slf4j @Component public class ModelSubmitHandler implements FormSubmitHandler<Serializable> { public String getSubmitType() { return "model"; } public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) { log.info("模型提交:userId={}, formInput={}", request.getUserId(), request.getFormInput()); // 模型创建成功后获得模型的 id Long modelId = createModel(request); return CommonPairResponse.success("模型提交成功!", modelId); } private Long createModel(FormSubmitRequest request) { // 创建模型的逻辑 return 123L; } }
在线预览表单提交:
@Slf4j @Component public class OnlinePreviewSubmitHandler implements FormSubmitHandler<Serializable> { public String getSubmitType() { return "online preview"; } public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) { log.info("在线预览提交:userId={}, formInput={}", request.getUserId(), request.getFormInput()); return CommonPairResponse.success("在线预览模式提交数据成功!", null); } }
绑定dubbo服务提交:
@Slf4j @Component public class DubboSubmitHandler implements FormSubmitHandler<Serializable> { public String getSubmitType() { return "dubbo"; } public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) { log.info("dubbo模式提交:userId={}, formInput={}", request.getUserId(), request.getFormInput()); // 进行dubbo调用,获得业务方返回的提示信息和业务数据 CommonPairResponse<String, Serializable> response = dubboSubmitData(request); return response; } }
4、建立策略的简单工厂
@Component public class FormSubmitHandlerFactory implements InitializingBean, ApplicationContextAware { private static final Map<String, FormSubmitHandler<Serializable>> FORM_SUBMIT_HANDLER_MAP = new HashMap<>(); private ApplicationContext applicationContext; /** * 根据提交类型获取对应的处理器 * @param submitType 提交类型 * @return 提交类型对应的处理器 */ public FormSubmitHandler<Serializable> getHandler(String submitType) { return FORM_SUBMIT_HANDLER_MAP.get(submitType); } public void afterPropertiesSet() throws Exception { // 将 Spring 容器中所有的 FormSubmitHandler 注册到 FORM_SUBMIT_HANDLER_MAP applicationContext.getBeansOfType(FormSubmitHandler.class).values().forEach( handler -> FORM_SUBMIT_HANDLER_MAP.put(handler.getSubmitType(), handler) ); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
我们让 FormSubmitHandlerFactory 实现 InitializingBean 接口,在 afterPropertiesSet 方法中,基于 Spring 容器将所有 FormSubmitHandler 自动注册到 FORM_SUBMIT_HANDLER_MAP,从而 Spring 容器启动完成后, getHandler 方法可以直接通过 submitType 来获取对应的表单提交处理器。
Factory 只负责获取 Handler,Handler 只负责处理具体的提交,Service 只负责逻辑编排, 从而达到功能上的 “低耦合高内聚”。
有木有感觉到,整个流程如丝滑般流畅~
5、假设扩展
如果业务方又需要加入一种提交策略,比如新增钩子函数提交。这对我们来说就相当easy了,我们只需要添加新的策略实现即可。
@Slf4j @Component public class HookSubmitHandler implements FormSubmitHandler<Serializable> { public String getSubmitType() { return "hook"; } public CommonPairResponse<String, Serializable> handleSubmit(FormSubmitRequest request) { log.info("hook钩子函数提交:userId={}, formInput={}", request.getUserId(), request.getFormInput()); // 进行 hook 函数调用,并获得业务方返回的提示信息和业务数据 CommonPairResponse<String, Serializable> response = hookSubmitData(request); return response; } }
此时不需要修改任何代码,因为Spring容器重启时会自动将HookSubmitHandler 注册到 FormSubmitHandlerFactory 中,后续业务方又要加其它策略的话,直接加策略实现,也不需要改动之前的代码,“低耦合高内聚”简直不要香。
欢迎小伙伴们关注我的公众号,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。
喜欢的话,点赞、再看、分享三连。