点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

作者:DiDi516

cnblogs.com/DiDi516/p/11787257.html

前言

物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。

这里枚举几种回执类型:MT1101、MT2101、MT4101、MT8104、MT8105、MT9999,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。当然,实际业务场景并没有那么笼统,这里以回执处理为演示案例

模拟一个回执类

@Data
public class Receipt {/*** 回执信息*/String message;/*** 回执类型(`MT1101、MT2101、MT4101、MT8104、MT8105、MT9999`)*/String type;}

模拟一个回执生成器

public class ReceiptBuilder {public static List<Receipt> generateReceiptList(){//直接模拟一堆回执对象List<Receipt> receiptList = new ArrayList<>();receiptList.add(new Receipt("我是MT2101回执喔","MT2101"));receiptList.add(new Receipt("我是MT1101回执喔","MT1101"));receiptList.add(new Receipt("我是MT8104回执喔","MT8104"));receiptList.add(new Receipt("我是MT9999回执喔","MT9999"));//......    return receiptList;}
}

传统做法-if-else分支

List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
//循环处理
for (Receipt receipt : receiptList) {if (StringUtils.equals("MT2101",receipt.getType())) {System.out.println("接收到MT2101回执");System.out.println("解析回执内容");System.out.println("执行业务逻辑");} else if (StringUtils.equals("MT1101",receipt.getType())) {System.out.println("接收到MT1101回执");System.out.println("解析回执内容");System.out.println("执行业务逻辑");} else if (StringUtils.equals("MT8104",receipt.getType())) {System.out.println("接收到MT8104回执");System.out.println("解析回执内容");System.out.println("执行业务逻辑");} else if (StringUtils.equals("MT9999",receipt.getType())) {System.out.println("接收到MT9999回执");System.out.println("解析回执内容");System.out.println("执行业务逻辑");System.out.println("推送邮件");}// ......未来可能还有好多个else if
}

在遇到if-else的分支业务逻辑比较复杂时,我们都习惯于将其抽出一个方法或者封装成一个对象去调用,这样整个if-else结构就不会显得太臃肿。

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

策略模式+Map字典

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

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

策略接口

/*** @Description: 回执处理策略接口* @Auther: wuzhazha*/
public interface IReceiptHandleStrategy {void handleReceipt(Receipt receipt);}

策略接口实现类,也就是具体的处理者

public class Mt2101ReceiptHandleStrategy implements IReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT2101:" + receipt.getMessage());}}public class Mt1101ReceiptHandleStrategy implements IReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT1101:" + receipt.getMessage());}}public class Mt8104ReceiptHandleStrategy implements IReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT8104:" + receipt.getMessage());}}public class Mt9999ReceiptHandleStrategy implements IReceiptHandleStrategy {@Overridepublic void handleReceipt(Receipt receipt) {System.out.println("解析报文MT9999:" + receipt.getMessage());}}

策略上下文类(策略接口的持有者)

/*** @Description: 上下文类,持有策略接口* @Auther: wuzhazha*/
public class ReceiptStrategyContext {private IReceiptHandleStrategy receiptHandleStrategy;/*** 设置策略接口* @param receiptHandleStrategy*/public void setReceiptHandleStrategy(IReceiptHandleStrategy receiptHandleStrategy) {this.receiptHandleStrategy = receiptHandleStrategy;}public void handleReceipt(Receipt receipt){if (receiptHandleStrategy != null) {receiptHandleStrategy.handleReceipt(receipt);   }}
}

策略工厂

/*** @Description: 策略工厂* @Auther: wuzhazha*/
public class ReceiptHandleStrategyFactory {private ReceiptHandleStrategyFactory(){}public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){IReceiptHandleStrategy receiptHandleStrategy = null;if (StringUtils.equals("MT2101",receiptType)) {receiptHandleStrategy = new Mt2101ReceiptHandleStrategy();} else if (StringUtils.equals("MT8104",receiptType)) {receiptHandleStrategy = new Mt8104ReceiptHandleStrategy();}return receiptHandleStrategy;}
}

客户端

public class Client {public static void main(String[] args) {//模拟回执List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();//策略上下文ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();for (Receipt receipt : receiptList) {//获取并设置策略IReceiptHandleStrategy receiptHandleStrategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());receiptStrategyContext.setReceiptHandleStrategy(receiptHandleStrategy);//执行策略receiptStrategyContext.handleReceipt(receipt);}}
}

解析报文MT2101:我是MT2101回执报文喔
解析报文MT8104:我是MT8104回执报文喔

由于我们的目的是消除if-else,那么这里需要将ReceiptHandleStrategyFactory策略工厂进行改造下,采用字典的方式存放我的策略,而Map具备key-value结构,采用Map是个不错选择。

稍微改造下,代码如下

/*** @Description: 策略工厂* @Auther: wuzhazha*/
public class ReceiptHandleStrategyFactory {private static Map<String,IReceiptHandleStrategy> receiptHandleStrategyMap;private ReceiptHandleStrategyFactory(){this.receiptHandleStrategyMap = new HashMap<>();this.receiptHandleStrategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());this.receiptHandleStrategyMap.put("MT8104",new Mt8104ReceiptHandleStrategy());}public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){return receiptHandleStrategyMap.get(receiptType);}
}

经过对策略模式+简单工厂方案的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。

如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去。系统学习设计模式:设计模式内容聚合

责任链模式

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

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

回执处理者接口

/*** @Description: 抽象回执处理者接口* @Auther: wuzhazha*/
public interface IReceiptHandler {void handleReceipt(Receipt receipt,IReceiptHandleChain handleChain);}

责任链接口

/*** @Description: 责任链接口* @Auther: wuzhazha*/
public interface IReceiptHandleChain {void handleReceipt(Receipt receipt);
}

责任链接口实现类

/*** @Description: 责任链实现类* @Auther: wuzhazha*/
public class ReceiptHandleChain implements IReceiptHandleChain {//记录当前处理者位置private int index = 0;//处理者集合private static List<IReceiptHandler> receiptHandlerList;static {//从容器中获取处理器对象receiptHandlerList = ReceiptHandlerContainer.getReceiptHandlerList();}@Overridepublic void handleReceipt(Receipt receipt) {if (receiptHandlerList !=null && receiptHandlerList.size() > 0) {if (index != receiptHandlerList.size()) {IReceiptHandler receiptHandler = receiptHandlerList.get(index++);receiptHandler.handleReceipt(receipt,this);}}}
}

具体回执处理者

public class Mt2101ReceiptHandler implements IReceiptHandler {@Overridepublic void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {if (StringUtils.equals("MT2101",receipt.getType())) {System.out.println("解析报文MT2101:" + receipt.getMessage());} //处理不了该回执就往下传递else {          handleChain.handleReceipt(receipt);}}
}public class Mt8104ReceiptHandler implements IReceiptHandler {@Overridepublic void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {if (StringUtils.equals("MT8104",receipt.getType())) {System.out.println("解析报文MT8104:" + receipt.getMessage());}//处理不了该回执就往下传递else {handleChain.handleReceipt(receipt);}}
}

责任链处理者容器(如果采用spring,则可以通过依赖注入的方式获取到IReceiptHandler的子类对象)

/*** @Description: 处理者容器* @Auther: wuzhazha*/
public class ReceiptHandlerContainer {private ReceiptHandlerContainer(){}public static List<IReceiptHandler> getReceiptHandlerList(){List<IReceiptHandler> receiptHandlerList = new ArrayList<>();receiptHandlerList.add(new Mt2101ReceiptHandler());receiptHandlerList.add(new Mt8104ReceiptHandler());return receiptHandlerList;}}

客户端

public class Client {public static void main(String[] args) {//模拟回执List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();for (Receipt receipt : receiptList) {//回执处理链对象ReceiptHandleChain receiptHandleChain = new ReceiptHandleChain();receiptHandleChain.handleReceipt(receipt);}}
}

解析报文MT2101:我是MT2101回执报文喔
解析报文MT8104:我是MT8104回执报文喔

通过责任链的处理方式,if-else结构也被我们消除了,每当新来了一种回执,只需要添加IReceiptHandler实现类并修改ReceiptHandlerContainer处理者容器即可,如果要使得程序符合开闭原则,则需要调整ReceiptHandlerContainer中处理者的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandler实现类。Java知音公众号内回复“后端面试”,送你一份面试宝典

这里使用到了一个反射工具类,用于获取指定接口的所有实现类

/*** @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() {@Overridepublic 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;}}

接下来改造ReceiptHandlerContainer

public class ReceiptHandlerContainer {private ReceiptHandlerContainer(){}public static List<IReceiptHandler> getReceiptHandlerList(){List<IReceiptHandler> receiptHandlerList = new ArrayList<>();//获取IReceiptHandler接口的实现类Set<Class<?>> classList = ReflectionUtil.getClassSetBySuper(IReceiptHandler.class);if (classList != null && classList.size() > 0) {for (Class<?> clazz : classList) {try {receiptHandlerList.add((IReceiptHandler)clazz.newInstance());} catch ( Exception e) {e.printStackTrace();}}}return receiptHandlerList;}}

至此,该方案完美符合了开闭原则,如果新增一个回执类型,只需要添加一个新的回执处理器即可,无需做其它改动。如新加了MT6666的回执,代码如下

public class Mt6666ReceiptHandler implements IReceiptHandler {@Overridepublic void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {if (StringUtils.equals("MT6666",receipt.getType())) {System.out.println("解析报文MT6666:" + receipt.getMessage());}//处理不了该回执就往下传递else {handleChain.handleReceipt(receipt);}}
}

策略模式+注解

此方案其实和上述没有太大异同,为了能符合开闭原则,通过自定义注解的方式,标记处理者类,然后反射获取到该类集合,放到Map容器中,这里不再赘述

小结

if-else或switch case 这种分支判断的方式对于分支逻辑不多的简单业务,还是直观高效的。对于业务复杂,分支逻辑多,采用适当的模式技巧,会让代码更加清晰,容易维护,但同时类或方法数量也是倍增的。我们需要对业务做好充分分析,避免一上来就设计模式,避免过度设计!

热门内容:数据库链接池终于搞对了,这次直接从100ms优化到3ms!
被面试官问懵B了,十亿级数据ES搜索怎么优化?
骚操作 | 不重启 JVM,替换掉已经加载的类,偷天换日?分布式锁用 Redis 还是 Zookeeper?写那么多年Java,还不知道啥是Java agent 的必须看一下!
科普|什么是负载均衡(Load balancing)最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

来吧,用设计模式来干掉 if-else相关推荐

  1. 如何巧用设计模式,干掉if-else

    点击关注公众号,利用碎片时间学习 前言 物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态). 这 ...

  2. 为什么像王者荣耀这样的游戏 Server 不愿意使用微服务?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:zhihu.com/question/359630395/a ...

  3. 微服务海量日志怎么处理,推荐你试试这款工具....

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者: 非洲羚羊 cnblogs.com/dengbangpang ...

  4. 数据持久化框架为什么放弃Hibernate、JPA、Mybatis,最终选择JDBCTemplate!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框 ...

  5. 涨姿势,Java中New一个对象是个怎么样的过程?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:勿念先生 blog.csdn.net/mohedong/ar ...

  6. 为什么很多 SpringBoot 开发者放弃了 Tomcat,选择了 Undertow?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:阿迈达 toutiao.com/a6775476659416 ...

  7. ElasticSearch的基本概念和集群分布式底层实现

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:张勇 http://tech.dianwoda.com/ 深 ...

  8. Spring Boot+JWT+Shiro+MyBatisPlus实现Restful快速开发后端脚手架

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:我叫刘半仙 链接:my.oschina.net/liughD ...

  9. 前、后端分离权限控制设计和实现思路

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:8rr.co/9QUT 简述 近几年随着react.angu ...

最新文章

  1. Linux查看多核CPU利用率
  2. Centos6.3下利用open***部署远程×××服务
  3. Jenkins简介安装使用
  4. ASP.NET 网站路径[转载]
  5. mfp 服务器控制中心,小身材大作用 固网USB打印服务器评测
  6. 为什么非全站升级HTTPS不可?
  7. 三天花三万!跟董事长女儿相亲却被骗财?世纪佳缘致歉杭州小吴并承诺赔偿...
  8. 在Axure中通过全局变量实现两个文本框与中继器联动
  9. caj 服务器正在运行中,紧急求助:caj阅读器打不开
  10. 韦达圆周率c语言,韦达对圆周率的表达式 – 手机爱问
  11. android N 移除 webview
  12. 28岁华为员工工资表曝光,牛逼的人注定会牛逼​!
  13. 【球迷福利】NBA球员数据分析
  14. SRT视频字幕的解析与同步
  15. Impala String函数大全
  16. 微博第三方登录 php,php 网站使用微博第三方授权登录
  17. DirectX游戏开发之代码的框架简析
  18. 天池比赛-01-用随机森林进行信贷违约预测-Baseline
  19. 北京尚学堂最新java大纲附python,框架项目视频教程资料
  20. 正版软件特惠活动最后几天~~需要赶紧入手!

热门文章

  1. Docker 数据卷之进阶篇
  2. Oracle中的substr()函数 详解及应用
  3. 深入理解C++中public、protected及private用法
  4. 《创新者》读书笔记 PB16110698 第五周(~4.5)
  5. document.onclick在ios上不触发的解决方法与touchstart点击穿透处理
  6. linux source命令
  7. 不能交换到解决jenkins用户的问题
  8. Visual C#访问接口
  9. LeetCode实战:最长公共前缀
  10. mysql8.0取消授权_mysql8创建用户、删除用户、授权、取消授权