策略模式史上最佳实践,没有之一!!!

简介: 策略模式史上最佳实践,没有之一!!!

一、背景


在软件开发中常常遇到这种情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。策略模式(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后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。


喜欢的话,点赞、再看、分享三连。

相关文章
|
NoSQL 网络协议 数据库
为什么 Lettuce 会带来更长的故障时间
本文详述了阿里云数据库 Tair/Redis 将使用长连接客户端在非预期故障宕机切换场景下的恢复时间从最初的 900s 降到 120s 再到 30s的优化过程,涉及产品优化,开源产品问题修复等诸多方面。
68021 11
为什么 Lettuce 会带来更长的故障时间
|
XML Java 测试技术
通义灵码与githubcopilot的对比评测
本文评测了通义灵码,与github copilot在一些代码编写能力上面的能力比较。 虽然github copilot要强很多,但灵码目前的能力也不算很弱,并且在一些小类上会做的更好一些。 值得试试看,也是免费的
58000 10
JDK8之stream流的使用:分组
JDK8之stream流的使用:分组
575 0
|
SQL Java 数据库连接
手把手带你写一个Mybatis框架
手把手带你写一个Mybatis框架
115 0
|
JSON NoSQL 关系型数据库
MongoDB常用命令大全,概述、备份恢复
MongoDB常用命令大全:服务启动停止、查看状态、备份;数据库相关,集合操作,文档操作,其他常用命令;数据备份恢复/导入导出——mongodump、mongorestore;MongoDB与SQL比较
|
7月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
766 0
|
12月前
|
Java
IDEA自定义配置注释模板,让你看起来更加专业!!!
IDEA自定义配置注释模板,让你看起来更加专业!!!
1235 0
|
11月前
|
Python
Python中的异步编程:使用asyncio和aiohttp实现高效网络请求
【10月更文挑战第34天】在Python的世界里,异步编程是提高效率的利器。本文将带你了解如何使用asyncio和aiohttp库来编写高效的网络请求代码。我们将通过一个简单的示例来展示如何利用这些工具来并发地处理多个网络请求,从而提高程序的整体性能。准备好让你的Python代码飞起来吧!
398 2
|
11月前
|
监控 Java
JavaGuide知识点整理——线程池的最佳实践
总之,合理使用和配置线程池是提高 Java 程序性能和稳定性的重要手段。遵循最佳实践,可以更好地发挥线程池的作用,提升系统的运行效率。同时,要不断地进行监控和优化,以适应不同的业务需求和环境变化。
410 63
|
10月前
|
存储 人工智能 API
(Elasticsearch)使用阿里云 infererence API 及 semantic text 进行向量搜索
本文我们展示了如何在Elasticsearch上使用阿里云 infererence API 及 semantic text 进行向量搜索。
183 10