设计模式学习(十二)用设计模式干掉 if-else,太优雅了!

目录

    • 一、场景举例
    • 二、什么时候需要改造 if-else?
    • 三、策略模式 + Map字典
      • 3.1 策略接口
      • 3.2 策略实现类
      • 3.3 策略工厂类(策略接口的持有者)
      • 3.4 客户端(测试类)
      • 3.5 执行结果
      • 3.6 总结
    • 四、责任链模式
      • 4.1 责任链处理接口
      • 4.2 责任链处理实现类
      • 4.3 责任链容器类(责任链处理接口持有者)
      • 4.4 客户端(测试类)
      • 4.5 执行结果
      • 4.6 总结
    • 五、补充:反射工具类

一、场景举例

假设有一个电商平台,再结算的时候需要根据不同的商品类型来计算优惠价格。平台定义了几种商品类型:ABCD。每种商品类型享受不同的优惠活动,需要根据商品类型来进行相应的计算。

if-else 代码实现:

/*** 处理订单* @param type  订单类型*/
public void handle(String type) {if (Objects.equals("normal", type)) {// 普通商品System.out.println("收到`普通商品`订单");System.out.println("开始解析`普通商品`订单内容");System.out.println("执行`普通商品`订单业务逻辑");System.out.println("`普通商品`订单处理完成");} else if (Objects.equals("discount", type)) {// 打折商品System.out.println("收到`打折商品`订单");System.out.println("开始解析`打折商品`订单内容");System.out.println("执行`打折商品`订单业务逻辑");System.out.println("`打折商品`订单处理完成");} else if (Objects.equals("fullReduction", type)) {// 满减商品System.out.println("收到`满减商品`订单");System.out.println("开始解析`满减商品`订单内容");System.out.println("执行`满减商品`订单业务逻辑");System.out.println("`满减商品`订单处理完成");} else if (Objects.equals("groupPurchase", type)) {// 团购商品System.out.println("收到`团购商品`订单");System.out.println("开始解析`团购商品`订单内容");System.out.println("执行`团购商品`订单业务逻辑");System.out.println("`团购商品`订单处理完成");}// ......未来可能还有好多个 else if
}

就上面例子,当订单类型越来越多时,分支 else if 就会越来越多,每增加一个订单类型,就需要修改或添加 if-else 分支,违反了开闭原则(对扩展开放,对修改关闭)


二、什么时候需要改造 if-else?

这里引用阿里规约对于什么时候改造 if-else 的一段话:

if-else 的代码违反了单一职责原则开闭原则,尤其是块中的代码量较大时,后续代码的扩展维护就会变得非常困难且容易出错,使用卫语句 也同样避免不了这两个问题。

因此根据经验,得出了一个我个人认为比较好的实践:

  • if-else 不超过 2 层,块中代码 1~5 行:直接写到 if-else 块中;
  • if-else 超过 2 层,块中代码不超过 3 行:尽量使用卫语句(封装为方法);
  • if-else 超过 2 层,块中代码超过 3 行:尽量使用状态模式、策略模式。

PS:卫语句如下所示:

public  handle(String type) {if (value == 1) {return true;}if (value == 2) {return true;}return false;
}

现在我们已经了解到什么场景下会需要改造 if-else 分支结构,那有哪些设计模式可以用来改造 if-else 分支结构呢?


三、策略模式 + Map字典

策略模式: 封装一系列的算法,它们具有共性,可以相互替换,也就是说让算法独立于使用它的客户端而独立变化,客户端仅仅依赖于策略接口。

在上述根据订单类型处理订单场景中,我们可以把 if-else 分支的业务逻辑抽取为各种策略,但是不可避免的是依然需要客户端写一些 if-else 进行策略选择的逻辑,我们可以将这段逻辑抽取到工厂类中去,这就是 策略模式+简单工厂,代码如下:

包结构如下:

在这里插入图片描述

3.1 策略接口

public interface IOrderHandlerStrategy {/*** 处理订单*/void handleOrder();
}

3.2 策略实现类

public class NormalOrderHandlerStrategy implements IOrderHandlerStrategy {@Overridepublic void handleOrder() {System.out.println("收到`普通商品`订单");System.out.println("开始解析`普通商品`订单内容");System.out.println("执行`普通商品`订单业务逻辑");System.out.println("`普通商品`订单处理完成");}
}public class DiscountOrderHandlerStrategy implements IOrderHandlerStrategy {@Overridepublic void handleOrder() {System.out.println("收到`打折商品`订单");System.out.println("开始解析`打折商品`订单内容");System.out.println("执行`打折商品`订单业务逻辑");System.out.println("`打折商品`订单处理完成");}
}public class FullReductionOrderHandlerStrategy implements IOrderHandlerStrategy {@Overridepublic void handleOrder() {System.out.println("收到`满减商品`订单");System.out.println("开始解析`满减商品`订单内容");System.out.println("执行`满减商品`订单业务逻辑");System.out.println("`满减商品`订单处理完成");}
}public class GroupPurchaseOrderHandlerStrategy implements IOrderHandlerStrategy {@Overridepublic void handleOrder() {System.out.println("收到`团购商品`订单");System.out.println("开始解析`团购商品`订单内容");System.out.println("执行`团购商品`订单业务逻辑");System.out.println("`团购商品`订单处理完成");}
}

3.3 策略工厂类(策略接口的持有者)

import java.util.HashMap;
import java.util.Map;/*** <p> @Title OrderHandlerStrategyContext* <p> @Description TODO** @author zhj* @date 2023/10/12 17:56*/
public class OrderHandlerStrategyFactory {/*** 订单处理策略*/private Map<String, IOrderHandlerStrategy> orderHandlerStrategyMap;/*** 构造方法,初始化策略*/public OrderHandlerStrategyFactory() {this.orderHandlerStrategyMap = new HashMap<>();orderHandlerStrategyMap.put("normal", new NormalOrderHandlerStrategy());orderHandlerStrategyMap.put("discount", new DiscountOrderHandlerStrategy());orderHandlerStrategyMap.put("groupPurchase", new GroupPurchaseOrderHandlerStrategy());orderHandlerStrategyMap.put("fullReduction", new FullReductionOrderHandlerStrategy());}/*** 获取订单处理策略** @param type 订单类型* @return 订单处理策略*/public IOrderHandlerStrategy getOrderHandlerStrategy(String type) {return orderHandlerStrategyMap.get(type);}
}

3.4 客户端(测试类)

public class Client {/*** 处理订单* @param type  订单类型*/public void handleWithStrategy(String type) {// 获取订单处理策略IOrderHandlerStrategy orderHandlerStrategy = new OrderHandlerStrategyFactory().getOrderHandlerStrategy(type);// 执行订单处理策略orderHandlerStrategy.handleOrder();}public static void main(String[] args) {Client client = new Client();client.handleWithStrategy("normal");}
}

3.5 执行结果

在这里插入图片描述

3.6 总结

  • 经过对策略模式+简单工厂方案的改造,我们已经消除了 if-else 的结构,每当 新加入一种处理方式,只需要 添加新的策略实现类,并修改工厂中的 Map 集合 即可。
  • 如果要使得程序符合开闭原则,则需要调整工厂类种处理策略的获取方式,通过反射Spring 注入的方式,获取指定包下的所有策略实现类,然后放到字典 Map 中

四、责任链模式

责任链模式: 是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下一个节点的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。

发出请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

包结构如下:

在这里插入图片描述

4.1 责任链处理接口

public interface IOrderHandler {/*** 处理订单*/void handleOrder();
}

4.2 责任链处理实现类

public class NormalOrderHandler implements IOrderHandler {@Overridepublic void handleOrder(String orderType, OrderHandlerChain chain) {// 普通商品if ("normal".equals(orderType)) {System.out.println("收到`普通商品`订单");System.out.println("开始解析`普通商品`订单内容");System.out.println("执行`普通商品`订单业务逻辑");System.out.println("`普通商品`订单处理完成");} else {chain.handleOrder(orderType);}}
}public class DiscountOrderHandler implements IOrderHandler {@Overridepublic void handleOrder(String orderType, OrderHandlerChain chain) {// 打折商品if ("discount".equals(orderType)) {System.out.println("收到`打折商品`订单");System.out.println("开始解析`打折商品`订单内容");System.out.println("执行`打折商品`订单业务逻辑");System.out.println("`打折商品`订单处理完成");} else {chain.handleOrder(orderType);}}
}public class GroupPurchaseOrderHandler implements IOrderHandler {@Overridepublic void handleOrder(String orderType, OrderHandlerChain chain) {// 团购商品if ("groupPurchase".equals(orderType)) {System.out.println("收到`团购商品`订单");System.out.println("开始解析`团购商品`订单内容");System.out.println("执行`团购商品`订单业务逻辑");System.out.println("`团购商品`订单处理完成");} else {chain.handleOrder(orderType);}}
}public class FullReductionOrderHandler implements IOrderHandler {@Overridepublic void handleOrder(String orderType, OrderHandlerChain chain) {// 满减商品if ("fullReduction".equals(orderType)) {System.out.println("收到`满减商品`订单");System.out.println("开始解析`满减商品`订单内容");System.out.println("执行`满减商品`订单业务逻辑");System.out.println("`满减商品`订单处理完成");} else {chain.handleOrder(orderType);}}
}

4.3 责任链容器类(责任链处理接口持有者)

import com.demo.test.handler.IOrderHandler;import java.util.ArrayList;
import java.util.List;public class OrderHandlerChain {/*** 订单处理器列表*/private List<IOrderHandler> orderHandlers = new ArrayList<>();/*** 订单处理器索引*/private ThreadLocal<Integer> index = ThreadLocal.withInitial(() -> 0);/*** 添加订单处理器** @param orderHandler 订单处理器*/public OrderHandlerChain addHandler(IOrderHandler orderHandler) {orderHandlers.add(orderHandler);return this;}/*** 执行订单处理器链** @param orderType 订单类型*/public void handleOrder(String orderType) {if (index.get() >= orderHandlers.size()) {// 重置索引index.set(0);return;}// 处理订单IOrderHandler orderHandler = orderHandlers.get(index.get());orderHandler.handleOrder(orderType, this);index.set(index.get() + 1);}
}

4.4 客户端(测试类)

public class Client {public static void main(String[] args) {OrderHandlerChain chain = new OrderHandlerChain();// 添加订单处理责任链chain.addHandler(new NormalOrderHandler()).addHandler(new DiscountOrderHandler()).addHandler(new GroupPurchaseOrderHandler()).addHandler(new FullReductionOrderHandler());chain.handleOrder("normal");}
}

4.5 执行结果

在这里插入图片描述

4.6 总结

  • 通过责任链的处理方式,if-else 结构也被消除了。每当 新加入一种处理方式,只需要 添加责任链处理接口的实现类,并修改责任链容器类 即可。
  • 如果要使得程序符合开闭原则,则需要调整责任链处理接口实现类的获取方式,通过反射Spring 注入的方式,获取指定包下的所有实现类

五、补充:反射工具类

这里补充一个反射工具类,用于获取 指定包某个接口所有实现类

/**  * @Description: 反射工具类  * @Auther: wuzhazha  */  
public class ReflectionUtil {  /**  * 定义类集合(用于存放所有加载的类)  */  private static final Set<Class<?>> CLASS_SET;  static {  //指定加载包路径  CLASS_SET = getClassSet("com.yaolong");  }  /**  * 获取类加载器  * @return  */  public static ClassLoader getClassLoader(){  return Thread.currentThread().getContextClassLoader();  }  /**  * 加载类  * @param className 类全限定名称  * @param isInitialized 是否在加载完成后执行静态代码块  * @return  */  public static Class<?> loadClass(String className,boolean isInitialized) {  Class<?> cls;  try {  cls = Class.forName(className,isInitialized,getClassLoader());  } catch (ClassNotFoundException e) {  throw new RuntimeException(e);  }  return cls;  }  public static Class<?> loadClass(String className) {  return loadClass(className,true);  }  /**  * 获取指定包下所有类  * @param packageName  * @return  */  public static Set<Class<?>> getClassSet(String packageName) {  Set<Class<?>> classSet = new HashSet<>();  try {  Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));  while (urls.hasMoreElements()) {  URL url = urls.nextElement();  if (url != null) {  String protocol = url.getProtocol();  if (protocol.equals("file")) {  String packagePath = url.getPath().replace("%20","");  addClass(classSet,packagePath,packageName);  } else if (protocol.equals("jar")) {  JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();  if (jarURLConnection != null) {  JarFile jarFile = jarURLConnection.getJarFile();  if (jarFile != null) {  Enumeration<JarEntry> jarEntries = jarFile.entries();  while (jarEntries.hasMoreElements()) {  JarEntry jarEntry = jarEntries.nextElement();  String jarEntryName = jarEntry.getName();  if (jarEntryName.endsWith(".class")) {  String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");  doAddClass(classSet,className);  }  }  }  }  }  }  }  } catch (IOException e) {  throw new RuntimeException(e);  }  return classSet;  }  private static void doAddClass(Set<Class<?>> classSet, String className) {  Class<?> cls = loadClass(className,false);  classSet.add(cls);  }  private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {  final File[] files = new File(packagePath).listFiles(new FileFilter() {  @Override  public boolean accept(File file) {  return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();  }  });  for (File file : files) {  String fileName = file.getName();  if (file.isFile()) {  String className = fileName.substring(0, fileName.lastIndexOf("."));  if (StringUtils.isNotEmpty(packageName)) {  className = packageName + "." + className;  }  doAddClass(classSet,className);  } else {  String subPackagePath = fileName;  if (StringUtils.isNotEmpty(packagePath)) {  subPackagePath = packagePath + "/" + subPackagePath;  }  String subPackageName = fileName;  if (StringUtils.isNotEmpty(packageName)) {  subPackageName = packageName + "." + subPackageName;  }  addClass(classSet,subPackagePath,subPackageName);  }  }  }  public static Set<Class<?>> getClassSet() {  return CLASS_SET;  }  /**  * 获取应用包名下某父类(或接口)的所有子类(或实现类)  * @param superClass  * @return  */  public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {  Set<Class<?>> classSet = new HashSet<>();  for (Class<?> cls : CLASS_SET) {  if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {  classSet.add(cls);  }  }  return classSet;  }  /**  * 获取应用包名下带有某注解的类  * @param annotationClass  * @return  */  public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {  Set<Class<?>> classSet = new HashSet<>();  for (Class<?> cls : CLASS_SET) {  if (cls.isAnnotationPresent(annotationClass)) {  classSet.add(cls);  }  }  return classSet;  }  }  

整理完毕,完结撒花~ 🌻





参考地址:

1.用设计模式干掉 if-else,太优雅了!https://mp.weixin.qq.com/s/LhfpxqiJZMEcnKDiOTwspg

2.策略模式:精妙替换你的if-else,https://zhuanlan.zhihu.com/p/453689771?utm_id=0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/156884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用【Blob、Base64】两种方式显示【文本、图片、视频】 使用 video 组件播放视频

Blob 显示 Blob 对象的类型是由 MIME 类型&#xff08;Multipurpose Internet Mail Extensions&#xff09;来确定的。MIME 类型是一种标准&#xff0c;用于表示文档、图像、音频、视频等多媒体文件的类型。以下是一些常见的 Blob 对象类型&#xff1a; text/plain&#xff1…

2024届通信工程保研经验分享(预推免入营即offer)

2024届通信工程保研经验分享&#xff08;预推免入营即offer&#xff09; BackGround夏令营情况&#xff1a;预推免情况&#xff1a; BackGround 本科院校&#xff1a;末九 专业&#xff1a;通信工程 rank&#xff1a;3/123&#xff08;预推免绩点排名&#xff09;&#xff0…

基于行波理论的输电线路防雷保护

摘要 随着科技的发展&#xff0c;电力已成为最重要的资源之一&#xff0c;如何保证电力的供应对于国民经济发展和人民生活水平的提高都有非常重要的意义。输电线路的防雷保护就是重点之一。架空输电线路分布很广&#xff0c;地处旷野&#xff0c;易遗受雷击&#xff0c;线路的雷…

Apache Ranger:(二)对Hive集成简单使用

1.Ranger Hive-plugin安装 进入 Ranger 编译生成的目录下 找到 ranger-2.0.0-hive-plugin.tar.gz 进行解压 tar -zxvf ranger-2.0.0-hive-plugin.tar.gz -C /opt/module/ 2.修改配置文件 vim install.properties #策略管理器的url地址 POLICY_MGR_URLhttp://[ip]:6080#组件…

Ubuntu 22.04.3 LTS单机私有化部署sealos

推荐使用奇数台 Master 节点和若干 Node 节点操作系统 :Ubuntu 22.04 LTS内核版本 :5.4 及以上配置推荐 :CPU 4 核 , 内存 8GB, 存储空间 100GB 以上最小配置 :CPU 2 核 , 内存 4GB, 存储空间 60GB 这里采用的Ubuntu 22.04.3 LTS 版本&#xff0c;Ubuntu 20.04.4 LTS这个版本…

铅华洗尽,粉黛不施,人工智能AI基于ProPainter技术去除图片以及视频水印(Python3.10)

视频以及图片修复技术是一项具有挑战性的AI视觉任务&#xff0c;它涉及在视频或者图片序列中填补缺失或损坏的区域&#xff0c;同时保持空间和时间的连贯性。该技术在视频补全、对象移除、视频恢复等领域有广泛应用。近年来&#xff0c;两种突出的方案在视频修复中崭露头角&…

【案例讲解】LVGL关于旋转的配置

更多源码分析请访问:LVGL 源码分析大全 目录 1、前言2、问题原因3、修改方案3.1 直接修改代码3.2 修改配置3.3 降低 LV_DISP_ROT_MAX_BUF 值1、前言 在实现项目中,因为横竖屏的关系,需要对其做90度的旋转。配置旋转时发现一些局部刷新控件,旋转后效果不对。 异常图标正常…

finalshell连接虚拟机中的ubuntu

finalshell下载地址: https://www.finalshell.org/ubuntu设置root密码&#xff1a; sudo passwd rootubuntu关闭防火墙&#xff1a; sudo ufw disable安装ssh # sudo apt update #更新数据(可以不执行) # sudo apt upgrade #更新软件(可以不执行) sudo apt install open…

ACDSee Photo Studio Ultimate 2024特别版(图片编辑器)

ACDSee Photo Studio Ultimate 2024是一款功能全面、易于使用的图像编辑和管理软件&#xff0c;为摄影师和设计师提供了强大的工具和功能。无论您是进行基本的图像优化还是进行复杂的创作&#xff0c;ACDSee Photo Studio Ultimate 2024都将成为您的得力助手。 软件下载&#x…

HTML5的新增表单元素

HTML5 有以下新的表单元素: <datalist> <keygen> <output> datalist datalist 元素规定输入域的选项列表。 datalist属性规定 form 或 input 域应该拥有自动完成功能。当用户在自动完成域中开始输入时&#xff0c;浏览器应该在该域中显示填写的选项&…

Python3无法调用Sqlalchemy解决(mysqldb)

原因 在安装Sqlalchemy后运行程序报错 无法导入mysqldb&#xff0c;缺失模块 ImportError: No module named ‘MySQLdb’ 既然缺少 MySQLdb 这个模块&#xff0c;尝试按照正常的想法执行 pip install MySQLdbpip install mysql-python 应该能解决&#xff0c;但是却找不到…

再谈Java泛型

一.类型参数的约束 我们可以对泛型传进来的参数做一些约束&#xff0c;比如说 用extends表明传进来的参数类型必须是必须是某个类型的子类型或者本身 当然也可以用接口约束&#xff0c;也是用extends表明传进来的参数类型必须实现某个接口。用&连接&#xff0c;注意class…

基于springboot实现校园闲置物品交易平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现校园闲置物品交易平台系统演示 摘要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的交易方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&am…

和鲸 ModelWhale 与华为 OceanStor 2910 计算型存储完成兼容性测试

数智化时代&#xff0c;数据总量的爆炸性增长伴随着人工智能、云计算等技术的发展&#xff0c;加速催化了公众对于数据存储与应用的多元化需求。同时&#xff0c;数据也是重要的基础资源和战略资源&#xff0c;需要严格保障其安全性、完整性。搭建国产数据基础设施底座&#xf…

13.SpringBoot项目之Service层

SpringBoot项目之Service层 JavaEE三层架构 为了项目维护方便&#xff0c;为了项目开发便利。三层架构功能控制器层&#xff1a;controller方便和前端数据进行交互业务层&#xff1a;service处理各种业务持久化层&#xff1a;mapper和数据库进行数据交互 抽取出service层 按…

使用格式工厂转换影片的默认音轨

不少电影尤其是mkv格式的都是英国双语的音轨&#xff0c;如图&#xff1a; 一般默认的是第一个English。有需求让它默认是国语的。 一、打开格式工厂 &#xff0c;选择视频格式&#xff0c;选择添加文件&#xff0c;选择输出配置 二、找到音频流索引 对应本文实例电影的音频顺…

XXE漏洞复现实操

文章目录 一、漏洞原理二、验证payload三、没有回显时的验证四、漏洞特征五、读取文件六、Base64加密读取七、端口检测八、使用php检测端口九、dtd外部实体读取文件十、Xxe漏洞防御 一、漏洞原理 (1)XXE漏洞全称XML External Entity Injection&#xff0c;即xmI外部实体注入漏…

Lego Studio打开solidworks零件/装配体 (sw另存obj文件)

solidworks 2020 Lego studio / part designer 截至2023-10-13&#x1f382;最新版 文章目录 操作步骤1&#xff09; solidworks 开启 ScanTo3D 功能2&#xff09; 零件 / 装配体 保存至stl格式文件3&#xff09; 以SanTo3D网格文件方式打开stl4&#xff09; 将打开的stl另存为…

ES相关面试问题整理

索引模板了解么 索引模板&#xff0c;一种复用机制&#xff0c;就像一些项目的开发框架如 Laravel 一样&#xff0c;省去了大量的重复&#xff0c;体力劳动。当新建一个 Elasticsearch 索引时&#xff0c;自动匹配模板&#xff0c;完成索引的基础部分搭建。 模板定义&#xf…

基于JavaWeb+SpringBoot+Vue超市管理系统的设计和实现

基于JavaWebSpringBootVue超市管理系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发…