如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了

2023-10-11 01:10

本文主要是介绍如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

整理了一些Java方面的架构、面试资料(微服务、集群、分布式、中间件等),有需要的小伙伴可以关注公众号【程序员内点事】,无套路自行领取

精彩回顾:

  • 一口气说出 9种 分布式ID生成方式,面试官有点懵了
  • 后端程序员不得不会的 Nginx 转发匹配规则
  • 基于 Java 实现的人脸识别功能(附源码)
  • 一口气说出 6种 @Transactional 注解失效场景
  • 干货推荐!程序员必备的13个 免费技术电子书网站

一、传统的实现方式

先说一下具体的需求:公司推广入口很多,每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源。

那么按照传统的实现方式代码就是如下:

public class OrderServiceImpl implements IOrderService {@Overridepublic String handle(OrderDTO dto) {String type = dto.getType();if ("1".equals(type)) {return "处理普通订单";} else if ("2".equals(type)) {return "处理团购订单";} else if ("3".equals(type)) {return "处理促销订单";}return null;}
}

为什么非得写的这么臃肿?很多同事会说:“哎呀,没办法呀,业务催的紧,这样开发效率快省事”。的确是句大实话,很多时候业务方确实像催命鬼一样的让你赶工期,想快速实现功能,这样写是最好的选择。

上边的代码看似还算清晰,可如果我告诉你公司订单来源有上百种,你想象一下那种臃肿的if-else,去翻代码时是什么感受?

二、策略模式的实现方式

策略模式是oop中最著名的设计模式之一,是对方法行为的抽象,可以归类为行为设计模式,也是oopinterface经典的应用。其特点简单又实用,是我最喜欢的模式之一。

策略模式定义了一个拥有共同行为的算法族,每个算法都被封装起来,可以互相替换,独立于客户端而变化。

不少人说:Java的设计模式背了很多,可日常还不就是写if-else的业务,根本就不用到。其实不是用不到是没有用到合适的位置!

1、策略模式的使用场景:
  • 针对同一问题的多种处理方式,仅仅是具体行为有差别时;
  • 需要安全地封装多种同一类型的操作时;
  • 同一抽象类有多个子类,而客户端需要使用if-else 或者 switch-case 来选择具体子类时。

这个是用策略模式修改后代码:

@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{@Autowiredprivate OrderStencilledService orderStencilledService;@Overridepublic void handle(OrderBO orderBO) {/*** 订单完结广播通知(1 - 支付完成)*/orderStencilledService.dispatchModeFanout(orderBO);/***  SCMS 出库单*/orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());}
}

每个订单来源都有自己单独的逻辑实现类,而每次需要添加订单来源,直接新建实现类,修改@OrderHandlerType(16)的数值即可,再也不用去翻那几百行的if-lese,一劳永逸!

2、具体的实现过程:

1、定义一个标识订单来源的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {int value() default 0;
}

2、抽象出来一个具体的业务处理器

public abstract class AbstractHandler {abstract public void handle(OrderBO orderBO);
}

3、项目启动扫描 handler 入口

@Component
@SuppressWarnings({"unused","rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {private String basePackage = "com.ecej.order.pipeline.processor";public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {Map<Integer,Class> map = new HashMap<Integer,Class>();ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{int type = x.getAnnotation(OrderHandlerType.class).value();map.put(type,x);});beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);log.info("处理器初始化{}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));}
}

4、扫描需要用到的工具类

public class ClassScaner {private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);/*** 添加包含的Fiter* @param includeFilter*/public void addIncludeFilter(TypeFilter includeFilter) {this.includeFilters.add(includeFilter);}/*** 添加排除的Fiter* @param includeFilter*/public void addExcludeFilter(TypeFilter excludeFilter) {this.excludeFilters.add(excludeFilter);}/*** 扫描指定的包,获取包下所有的Class* @param basePackage 包名* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解* @return Set<Class<?>>*/public static Set<Class<?>> scan(String basePackage,Class<?>... targetTypes) {ClassScaner cs = new ClassScaner();for (Class<?> targetType : targetTypes){if(TypeUtils.isAssignable(Annotation.class, targetType)){cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));}else{cs.addIncludeFilter(new AssignableTypeFilter(targetType));}}return cs.doScan(basePackage);}/*** 扫描指定的包,获取包下所有的Class* @param basePackages 包名,多个* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解* @return Set<Class<?>>*/public static Set<Class<?>> scan(String[] basePackages,Class<?>... targetTypes) {ClassScaner cs = new ClassScaner();for (Class<?> targetType : targetTypes){if(TypeUtils.isAssignable(Annotation.class, targetType)){cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));}else{cs.addIncludeFilter(new AssignableTypeFilter(targetType));}}Set<Class<?>> classes = new HashSet<Class<?>>();for (String s : basePackages){classes.addAll(cs.doScan(s));}return classes;}/*** 扫描指定的包,获取包下所有的Class* @param basePackages 包名* @return Set<Class<?>>*/public Set<Class<?>> doScan(String [] basePackages) {Set<Class<?>> classes = new HashSet<Class<?>>();for (String basePackage :basePackages) {classes.addAll(doScan(basePackage));}return classes;}/*** 扫描指定的包,获取包下所有的Class* @param basePackages 包名* @return Set<Class<?>>*/public Set<Class<?>> doScan(String basePackage) {Set<Class<?>> classes = new HashSet<Class<?>>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+ ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);for (int i = 0; i < resources.length; i++) {Resource resource = resources[i];if (resource.isReadable()) {MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {try {classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));} catch (ClassNotFoundException ignore) {}}}}} catch (IOException ex) {throw new RuntimeException("I/O failure during classpath scanning", ex);}return classes;}/*** 处理 excludeFilters和includeFilters* @param metadataReader* @return boolean* @throws IOException*/private boolean matches(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, this.metadataReaderFactory)) {return true;}}return false;}
}

5、 根据类型实例化抽象类


@Component
public class HandlerContext {@Autowiredprivate ApplicationContext beanFactory;public  AbstractHandler getInstance(Integer type){Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());return (AbstractHandler)beanFactory.getBean(map.get(type));}}

6、调用入口,我这里是接的MQ消息,会批量的处理多个订单来源

@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);@Autowiredprivate HandlerContext HandlerContext;@Autowiredprivate OrderValidateService orderValidateService;@RabbitHandlerpublic void subscribeMessage(MessageBean bean){OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);if(null != orderBO &&CollectionUtils.isNotEmpty(bean.getType())){for(int value:bean.getType()){AbstractHandler handler = HandlerContext.getInstance(value);handler.handle(orderBO);}}}
}

接收实体 MessageBean 类代码

public class MessageBean implements Serializable {private static final long serialVersionUID = 5454831432308782668L;private String cachKey;private List<Integer> type;private String orderBO;public MessageBean(List<Integer> type, String orderBO) {this.type = type;this.orderBO = orderBO;}
}
三、策略模式的优缺点
优点
  • 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
  • 避免使用多重条件选择语句,充分体现面向对象设计思想 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
  • 每个策略类使用一个策略类,符合单一职责原则 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
  • 客户端不需要知道都有哪些策略类,符合最小知识原则
缺点
  • 策略模式,当策略算法太多时,会造成很多的策略类
  • 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过封装common公共包解决,也可以考虑使IOC容器依赖注入的方式来解决。

以下是订单来源策略类的一部分,不得不说策略类确实比较多。
在这里插入图片描述

总结:

凡事都有他的两面性,if-else多层嵌套和也都有其各自的优缺点:

if-else的有点就是简单,想快速迭代功能,逻辑嵌套少且不会持续增加,if-else更好些,缺点也是显而易见,代码臃肿繁琐不便于维护。

策略模式 将各个场景的逻辑剥离出来维护,同一抽象类有多个子类,需要使用if-else 或者 switch-case 来选择具体子类时,建议选策略模式,他的缺点就是会产生比较多的策略类文件。

两种实现方式各有利弊,如何选择还是要依据具体业务场景,还是那句话设计模式不是为了用而用,一定要用在最合适的位置。


今天就说这么多,如果本文对您有一点帮助,希望能得到您一个点赞👍哦

您的认可才是我写作的动力!

小福利:

通过合法手段,获取到一些极客付费课程 ,嘘~,免费 送给小伙伴们。公众号回复【极客】自行领取

整理了一些Java方面的架构、面试资料,有需要的小伙伴可以关注公众号【程序员内点事

这篇关于如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/184435

相关文章

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

python展开嵌套列表的多种方法

《python展开嵌套列表的多种方法》本文主要介绍了python展开嵌套列表的多种方法,包括for循环、列表推导式和sum函数三种方法,具有一定的参考价值,感兴趣的可以了解一下... 目录一、嵌套列表格式二、嵌套列表展开方法(一)for循环(1)for循环+append()(2)for循环+pyPhWiFd

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

Java嵌套for循环优化方案分享

《Java嵌套for循环优化方案分享》介绍了Java中嵌套for循环的优化方法,包括减少循环次数、合并循环、使用更高效的数据结构、并行处理、预处理和缓存、算法优化、尽量减少对象创建以及本地变量优化,通... 目录Java 嵌套 for 循环优化方案1. 减少循环次数2. 合并循环3. 使用更高效的数据结构4

Deepseek使用指南与提问优化策略方式

《Deepseek使用指南与提问优化策略方式》本文介绍了DeepSeek语义搜索引擎的核心功能、集成方法及优化提问策略,通过自然语言处理和机器学习提供精准搜索结果,适用于智能客服、知识库检索等领域... 目录序言1. DeepSeek 概述2. DeepSeek 的集成与使用2.1 DeepSeek API

Redis的数据过期策略和数据淘汰策略

《Redis的数据过期策略和数据淘汰策略》本文主要介绍了Redis的数据过期策略和数据淘汰策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录一、数据过期策略1、惰性删除2、定期删除二、数据淘汰策略1、数据淘汰策略概念2、8种数据淘汰策略