用设计模式来干掉 if-else

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

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

模拟一个回执类

  1. @Data
  2. public class Receipt {
  3. /**
  4. * 回执信息
  5. */
  6. String message;
  7. /**
  8. * 回执类型(`MT1101、MT2101、MT4101、MT8104、MT8105、MT9999`)
  9. */
  10. String type;
  11. }

模拟一个回执生成器

  1. public class ReceiptBuilder {
  2. public static List<Receipt> generateReceiptList(){
  3. //直接模拟一堆回执对象
  4. List<Receipt> receiptList = new ArrayList<>();
  5. receiptList.add(new Receipt("我是MT2101回执喔","MT2101"));
  6. receiptList.add(new Receipt("我是MT1101回执喔","MT1101"));
  7. receiptList.add(new Receipt("我是MT8104回执喔","MT8104"));
  8. receiptList.add(new Receipt("我是MT9999回执喔","MT9999"));
  9. //......
  10. return receiptList;
  11. }
  12. }

传统做法 if-else 分支

  1. List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
  2. //循环处理
  3. for (Receipt receipt : receiptList) {
  4. if (StringUtils.equals("MT2101",receipt.getType())) {
  5. System.out.println("接收到MT2101回执");
  6. System.out.println("解析回执内容");
  7. System.out.println("执行业务逻辑");
  8. } else if (StringUtils.equals("MT1101",receipt.getType())) {
  9. System.out.println("接收到MT1101回执");
  10. System.out.println("解析回执内容");
  11. System.out.println("执行业务逻辑");
  12. } else if (StringUtils.equals("MT8104",receipt.getType())) {
  13. System.out.println("接收到MT8104回执");
  14. System.out.println("解析回执内容");
  15. System.out.println("执行业务逻辑");
  16. } else if (StringUtils.equals("MT9999",receipt.getType())) {
  17. System.out.println("接收到MT9999回执");
  18. System.out.println("解析回执内容");
  19. System.out.println("执行业务逻辑");
  20. System.out.println("推送邮件");
  21. }
  22. // ......未来可能还有好多个else if
  23. }

在遇到if-else的分支业务逻辑比较复杂时,我们都习惯于将其抽出一个方法或者封装成一个对象去调用,这样整个if-else结构就不会显得太臃肿。就上面例子,当回执的类型越来越多时,分支else if 就会越来越多,每增加一个回执类型,就需要修改或添加if-else分支,违反了开闭原则(对扩展开放,对修改关闭)。

策略模式+Map字典

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

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

策略接口

  1. /**
  2. * @Description: 回执处理策略接口
  3. */
  4. public interface IReceiptHandleStrategy {
  5. void handleReceipt(Receipt receipt);
  6. }

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

  1. public class Mt2101ReceiptHandleStrategy implements IReceiptHandleStrategy {
  2. @Override
  3. public void handleReceipt(Receipt receipt) {
  4. System.out.println("解析报文MT2101:" + receipt.getMessage());
  5. }
  6. }
  7. public class Mt1101ReceiptHandleStrategy implements IReceiptHandleStrategy {
  8. @Override
  9. public void handleReceipt(Receipt receipt) {
  10. System.out.println("解析报文MT1101:" + receipt.getMessage());
  11. }
  12. }
  13. public class Mt8104ReceiptHandleStrategy implements IReceiptHandleStrategy {
  14. @Override
  15. public void handleReceipt(Receipt receipt) {
  16. System.out.println("解析报文MT8104:" + receipt.getMessage());
  17. }
  18. }
  19. public class Mt9999ReceiptHandleStrategy implements IReceiptHandleStrategy {
  20. @Override
  21. public void handleReceipt(Receipt receipt) {
  22. System.out.println("解析报文MT9999:" + receipt.getMessage());
  23. }
  24. }

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

  1. /**
  2. * @Description: 上下文类,持有策略接口
  3. */
  4. public class ReceiptStrategyContext {
  5. private IReceiptHandleStrategy receiptHandleStrategy;
  6. /**
  7. * 设置策略接口
  8. * @param receiptHandleStrategy
  9. */
  10. public void setReceiptHandleStrategy(IReceiptHandleStrategy receiptHandleStrategy) {
  11. this.receiptHandleStrategy = receiptHandleStrategy;
  12. }
  13. public void handleReceipt(Receipt receipt){
  14. if (receiptHandleStrategy != null) {
  15. receiptHandleStrategy.handleReceipt(receipt);
  16. }
  17. }
  18. }

策略工厂

  1. /**
  2. * @Description: 策略工厂
  3. */
  4. public class ReceiptHandleStrategyFactory {
  5. private ReceiptHandleStrategyFactory(){}
  6. public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
  7. IReceiptHandleStrategy receiptHandleStrategy = null;
  8. if (StringUtils.equals("MT2101",receiptType)) {
  9. receiptHandleStrategy = new Mt2101ReceiptHandleStrategy();
  10. } else if (StringUtils.equals("MT8104",receiptType)) {
  11. receiptHandleStrategy = new Mt8104ReceiptHandleStrategy();
  12. }
  13. return receiptHandleStrategy;
  14. }
  15. }

客户端

  1. public class Client {
  2. public static void main(String[] args) {
  3. //模拟回执
  4. List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
  5. //策略上下文
  6. ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();
  7. for (Receipt receipt : receiptList) {
  8. //获取并设置策略
  9. IReceiptHandleStrategy receiptHandleStrategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
  10. receiptStrategyContext.setReceiptHandleStrategy(receiptHandleStrategy);
  11. //执行策略
  12. receiptStrategyContext.handleReceipt(receipt);
  13. }
  14. }
  15. }

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

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

稍微改造下,代码如下

  1. /**
  2. * @Description: 策略工厂
  3. */
  4. public class ReceiptHandleStrategyFactory {
  5. private static Map<String,IReceiptHandleStrategy> receiptHandleStrategyMap;
  6. private ReceiptHandleStrategyFactory(){
  7. this.receiptHandleStrategyMap = new HashMap<>();
  8. this.receiptHandleStrategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
  9. this.receiptHandleStrategyMap.put("MT8104",new Mt8104ReceiptHandleStrategy());
  10. }
  11. public static IReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
  12. return receiptHandleStrategyMap.get(receiptType);
  13. }
  14. }

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

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

责任链模式

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

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

回执处理者接口

  1. /**
  2. * @Description: 抽象回执处理者接口
  3. */
  4. public interface IReceiptHandler {
  5. void handleReceipt(Receipt receipt,IReceiptHandleChain handleChain);
  6. }

责任链接口

  1. /**
  2. * @Description: 责任链接口
  3. */
  4. public interface IReceiptHandleChain {
  5. void handleReceipt(Receipt receipt);
  6. }

责任链接口实现类

  1. /**
  2. * @Description: 责任链实现类
  3. */
  4. public class ReceiptHandleChain implements IReceiptHandleChain {
  5. //记录当前处理者位置
  6. private int index = 0;
  7. //处理者集合
  8. private static List<IReceiptHandler> receiptHandlerList;
  9. static {
  10. //从容器中获取处理器对象
  11. receiptHandlerList = ReceiptHandlerContainer.getReceiptHandlerList();
  12. }
  13. @Override
  14. public void handleReceipt(Receipt receipt) {
  15. if (receiptHandlerList !=null && receiptHandlerList.size() > 0) {
  16. if (index != receiptHandlerList.size()) {
  17. IReceiptHandler receiptHandler = receiptHandlerList.get(index++);
  18. receiptHandler.handleReceipt(receipt,this);
  19. }
  20. }
  21. }
  22. }

具体回执处理者

  1. public class Mt2101ReceiptHandler implements IReceiptHandler {
  2. @Override
  3. public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
  4. if (StringUtils.equals("MT2101",receipt.getType())) {
  5. System.out.println("解析报文MT2101:" + receipt.getMessage());
  6. }
  7. //处理不了该回执就往下传递
  8. else {
  9. handleChain.handleReceipt(receipt);
  10. }
  11. }
  12. }
  13. public class Mt8104ReceiptHandler implements IReceiptHandler {
  14. @Override
  15. public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
  16. if (StringUtils.equals("MT8104",receipt.getType())) {
  17. System.out.println("解析报文MT8104:" + receipt.getMessage());
  18. }
  19. //处理不了该回执就往下传递
  20. else {
  21. handleChain.handleReceipt(receipt);
  22. }
  23. }
  24. }

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

  1. /**
  2. * @Description: 处理者容器
  3. * @Auther: wuzhazha
  4. */
  5. public class ReceiptHandlerContainer {
  6. private ReceiptHandlerContainer(){}
  7. public static List<IReceiptHandler> getReceiptHandlerList(){
  8. List<IReceiptHandler> receiptHandlerList = new ArrayList<>();
  9. receiptHandlerList.add(new Mt2101ReceiptHandler());
  10. receiptHandlerList.add(new Mt8104ReceiptHandler());
  11. return receiptHandlerList;
  12. }
  13. }

客户端

  1. public class Client {
  2. public static void main(String[] args) {
  3. //模拟回执
  4. List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();
  5. for (Receipt receipt : receiptList) {
  6. //回执处理链对象
  7. ReceiptHandleChain receiptHandleChain = new ReceiptHandleChain();
  8. receiptHandleChain.handleReceipt(receipt);
  9. }
  10. }
  11. }

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

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

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

  1. /**
  2. * @Description: 反射工具类
  3. * @Auther: wuzhazha
  4. */
  5. public class ReflectionUtil {
  6. /**
  7. * 定义类集合(用于存放所有加载的类)
  8. */
  9. private static final Set<Class<?>> CLASS_SET;
  10. static {
  11. //指定加载包路径
  12. CLASS_SET = getClassSet("com.yaolong");
  13. }
  14. /**
  15. * 获取类加载器
  16. * @return
  17. */
  18. public static ClassLoader getClassLoader(){
  19. return Thread.currentThread().getContextClassLoader();
  20. }
  21. /**
  22. * 加载类
  23. * @param className 类全限定名称
  24. * @param isInitialized 是否在加载完成后执行静态代码块
  25. * @return
  26. */
  27. public static Class<?> loadClass(String className,boolean isInitialized) {
  28. Class<?> cls;
  29. try {
  30. cls = Class.forName(className,isInitialized,getClassLoader());
  31. } catch (ClassNotFoundException e) {
  32. throw new RuntimeException(e);
  33. }
  34. return cls;
  35. }
  36. public static Class<?> loadClass(String className) {
  37. return loadClass(className,true);
  38. }
  39. /**
  40. * 获取指定包下所有类
  41. * @param packageName
  42. * @return
  43. */
  44. public static Set<Class<?>> getClassSet(String packageName) {
  45. Set<Class<?>> classSet = new HashSet<>();
  46. try {
  47. Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
  48. while (urls.hasMoreElements()) {
  49. URL url = urls.nextElement();
  50. if (url != null) {
  51. String protocol = url.getProtocol();
  52. if (protocol.equals("file")) {
  53. String packagePath = url.getPath().replace("%20","");
  54. addClass(classSet,packagePath,packageName);
  55. } else if (protocol.equals("jar")) {
  56. JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
  57. if (jarURLConnection != null) {
  58. JarFile jarFile = jarURLConnection.getJarFile();
  59. if (jarFile != null) {
  60. Enumeration<JarEntry> jarEntries = jarFile.entries();
  61. while (jarEntries.hasMoreElements()) {
  62. JarEntry jarEntry = jarEntries.nextElement();
  63. String jarEntryName = jarEntry.getName();
  64. if (jarEntryName.endsWith(".class")) {
  65. String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
  66. doAddClass(classSet,className);
  67. }
  68. }
  69. }
  70. }
  71. }
  72. }
  73. }
  74. } catch (IOException e) {
  75. throw new RuntimeException(e);
  76. }
  77. return classSet;
  78. }
  79. private static void doAddClass(Set<Class<?>> classSet, String className) {
  80. Class<?> cls = loadClass(className,false);
  81. classSet.add(cls);
  82. }
  83. private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
  84. final File[] files = new File(packagePath).listFiles(new FileFilter() {
  85. @Override
  86. public boolean accept(File file) {
  87. return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
  88. }
  89. });
  90. for (File file : files) {
  91. String fileName = file.getName();
  92. if (file.isFile()) {
  93. String className = fileName.substring(0, fileName.lastIndexOf("."));
  94. if (StringUtils.isNotEmpty(packageName)) {
  95. className = packageName + "." + className;
  96. }
  97. doAddClass(classSet,className);
  98. } else {
  99. String subPackagePath = fileName;
  100. if (StringUtils.isNotEmpty(packagePath)) {
  101. subPackagePath = packagePath + "/" + subPackagePath;
  102. }
  103. String subPackageName = fileName;
  104. if (StringUtils.isNotEmpty(packageName)) {
  105. subPackageName = packageName + "." + subPackageName;
  106. }
  107. addClass(classSet,subPackagePath,subPackageName);
  108. }
  109. }
  110. }
  111. public static Set<Class<?>> getClassSet() {
  112. return CLASS_SET;
  113. }
  114. /**
  115. * 获取应用包名下某父类(或接口)的所有子类(或实现类)
  116. * @param superClass
  117. * @return
  118. */
  119. public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
  120. Set<Class<?>> classSet = new HashSet<>();
  121. for (Class<?> cls : CLASS_SET) {
  122. if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
  123. classSet.add(cls);
  124. }
  125. }
  126. return classSet;
  127. }
  128. /**
  129. * 获取应用包名下带有某注解的类
  130. * @param annotationClass
  131. * @return
  132. */
  133. public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
  134. Set<Class<?>> classSet = new HashSet<>();
  135. for (Class<?> cls : CLASS_SET) {
  136. if (cls.isAnnotationPresent(annotationClass)) {
  137. classSet.add(cls);
  138. }
  139. }
  140. return classSet;
  141. }
  142. }

接下来改造ReceiptHandlerContainer

  1. public class ReceiptHandlerContainer {
  2. private ReceiptHandlerContainer(){}
  3. public static List<IReceiptHandler> getReceiptHandlerList(){
  4. List<IReceiptHandler> receiptHandlerList = new ArrayList<>();
  5. //获取IReceiptHandler接口的实现类
  6. Set<Class<?>> classList = ReflectionUtil.getClassSetBySuper(IReceiptHandler.class);
  7. if (classList != null && classList.size() > 0) {
  8. for (Class<?> clazz : classList) {
  9. try {
  10. receiptHandlerList.add((IReceiptHandler)clazz.newInstance());
  11. } catch ( Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. return receiptHandlerList;
  17. }
  18. }

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

  1. public class Mt6666ReceiptHandler implements IReceiptHandler {
  2. @Override
  3. public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {
  4. if (StringUtils.equals("MT6666",receipt.getType())) {
  5. System.out.println("解析报文MT6666:" + receipt.getMessage());
  6. }
  7. //处理不了该回执就往下传递
  8. else {
  9. handleChain.handleReceipt(receipt);
  10. }
  11. }
  12. }

策略模式+注解

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

小结

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