用設計模式干掉 if-else,太優(yōu)雅了!

物流行業(yè)中,通常會涉及到EDI報文(XML格式文件)傳輸和回執(zhí)接收,每發(fā)送一份EDI報文,后續(xù)都會收到與之關聯(lián)的回執(zhí)(標識該數據在第三方系統(tǒng)中的流轉狀態(tài))。

這里枚舉幾種回執(zhí)類型:MT1101、MT2101、MT4101、MT8104、MT8105、MT9999,系統(tǒng)在收到不同的回執(zhí)報文后,會執(zhí)行對應的業(yè)務邏輯處理。當然,實際業(yè)務場景并沒有那么籠統(tǒng),這里以回執(zhí)處理為演示案例。

模擬一個回執(zhí)類

@Data  
public class Receipt {  

    /**  
     * 回執(zhí)信息  
     */  
    String message;  

    /**  
     * 回執(zhí)類型(`MT1101、MT2101、MT4101、MT8104、MT8105、MT9999`)  
     */  
    String type;  

} 

模擬一個回執(zhí)生成器

public class ReceiptBuilder {  

    public static List<Receipt> generateReceiptList(){  
        //直接模擬一堆回執(zhí)對象  
        List<Receipt> receiptList = new ArrayList<>();  
        receiptList.add(new Receipt("我是MT2101回執(zhí)喔","MT2101"));  
        receiptList.add(new Receipt("我是MT1101回執(zhí)喔","MT1101"));  
        receiptList.add(new Receipt("我是MT8104回執(zhí)喔","MT8104"));  
        receiptList.add(new Receipt("我是MT9999回執(zhí)喔","MT9999"));  
        //......  
        return receiptList;  
    }  
}

# [傳統(tǒng)做法-if-else分支]


List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();  
//循環(huán)處理  
for (Receipt receipt : receiptList) {  
    if (StringUtils.equals("MT2101",receipt.getType())) {  
        System.out.println("接收到MT2101回執(zhí)");  
        System.out.println("解析回執(zhí)內容");  
        System.out.println("執(zhí)行業(yè)務邏輯");  
    } else if (StringUtils.equals("MT1101",receipt.getType())) {  
        System.out.println("接收到MT1101回執(zhí)");  
        System.out.println("解析回執(zhí)內容");  
        System.out.println("執(zhí)行業(yè)務邏輯");  
    } else if (StringUtils.equals("MT8104",receipt.getType())) {  
        System.out.println("接收到MT8104回執(zhí)");  
        System.out.println("解析回執(zhí)內容");  
        System.out.println("執(zhí)行業(yè)務邏輯");  
    } else if (StringUtils.equals("MT9999",receipt.getType())) {  
        System.out.println("接收到MT9999回執(zhí)");  
        System.out.println("解析回執(zhí)內容");  
        System.out.println("執(zhí)行業(yè)務邏輯");  
        System.out.println("推送郵件");  
    }  
    // ......未來可能還有好多個else if  
}

在遇到if-else的分支業(yè)務邏輯比較復雜時,我們都習慣于將其抽出一個方法或者封裝成一個對象去調用,這樣整個if-else結構就不會顯得太臃腫

就上面例子,當回執(zhí)的類型越來越多時,分支else if 就會越來越多,每增加一個回執(zhí)類型,就需要修改或添加if-else分支,違反了開閉原則(對擴展開放,對修改關閉)。

策略模式+Map字典

我們知道, 策略模式的目的是封裝一系列的算法,它們具有共性,可以相互替換,也就是說讓算法獨立于使用它的客戶端而獨立變化,客戶端僅僅依賴于策略接口 。

在上述場景中,我們可以把if-else分支的業(yè)務邏輯抽取為各種策略,但是不可避免的是依然需要客戶端寫一些if-else進行策略選擇的邏輯,我們可以將這段邏輯抽取到工廠類中去,這就是策略模式+簡單工廠,代碼如下。

推薦一個開源免費的 Spring Boot 實戰(zhàn)項目:https://github.com/javastacks/spring-boot-best-practice

策略接口

/**  
 * @Description: 回執(zhí)處理策略接口  
 * @Auther: wuzhazha  
 */  
public interface IReceiptHandleStrategy {  

    void handleReceipt(Receipt receipt);  

}

策略接口實現類,也就是具體的處理者

public class Mt2101ReceiptHandleStrategy implements IReceiptHandleStrategy {  

    @Override  
    public void handleReceipt(Receipt receipt) {  
        System.out.println("解析報文MT2101:" + receipt.getMessage());  
    }  

}  

public class Mt1101ReceiptHandleStrategy implements IReceiptHandleStrategy {  

    @Override  
    public void handleReceipt(Receipt receipt) {  
        System.out.println("解析報文MT1101:" + receipt.getMessage());  
    }  

}  

public class Mt8104ReceiptHandleStrategy implements IReceiptHandleStrategy {  

    @Override  
    public void handleReceipt(Receipt receipt) {  
        System.out.println("解析報文MT8104:" + receipt.getMessage());  
    }  

}  

public class Mt9999ReceiptHandleStrategy implements IReceiptHandleStrategy {  

    @Override  
    public 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) {  
        //模擬回執(zhí)  
        List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();  
        //策略上下文  
        ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();  
        for (Receipt receipt : receiptList) {  
            //獲取并設置策略  
            IReceiptHandleStrategy receiptHandleStrategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());  
            receiptStrategyContext.setReceiptHandleStrategy(receiptHandleStrategy);  
            //執(zhí)行策略  
            receiptStrategyContext.handleReceipt(receipt);  
        }  
    }  
}

由于我們的目的是消除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的結構,每當新來了一種回執(zhí),只需要添加新的回執(zhí)處理策略,并修改ReceiptHandleStrategyFactory中的Map集合。

如果要使得程序符合開閉原則,則需要調整ReceiptHandleStrategyFactory中處理策略的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandleStrategy實現類,然后放到字典Map中去。

責任鏈模式

責任鏈模式是一種對象的行為模式。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

發(fā)出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求,這使得系統(tǒng)可以在不影響客戶端的情況下動態(tài)地重新組織和分配責任

回執(zhí)處理者接口

/**  
 * @Description: 抽象回執(zhí)處理者接口   
*@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();     
 }        
@Override      
public void handleReceipt(Receipt receipt) {         
 if (receiptHandlerList !=null && receiptHandlerList.size() > 0) {             
 if (index != receiptHandlerList.size()) {                  
IReceiptHandler receiptHandler = receiptHandlerList.get(index++);                  
receiptHandler.handleReceipt(receipt,this);             
 }          
}    
  } 
 }  

具體回執(zhí)處理者

public class Mt2101ReceiptHandler implements IReceiptHandler {        
@Override      
public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {         
 if (StringUtils.equals("MT2101",receipt.getType())) {              
System.out.println("解析報文MT2101:" + receipt.getMessage());         
 }          
//處理不了該回執(zhí)就往下傳遞          
else {             
 handleChain.handleReceipt(receipt);         
 }     
 } 
 }    
public class Mt8104ReceiptHandler implements IReceiptHandler {  
      @Override      
public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {         
 if (StringUtils.equals("MT8104",receipt.getType())) {              
System.out.println("解析報文MT8104:" + receipt.getMessage());         
 }          
//處理不了該回執(zhí)就往下傳遞         
 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) {          
//模擬回執(zhí)         
 List<Receipt> receiptList = ReceiptBuilder.generateReceiptList();         
 for (Receipt receipt : receiptList) {            
  //回執(zhí)處理鏈對象              
ReceiptHandleChain receiptHandleChain = new ReceiptHandleChain();              
receiptHandleChain.handleReceipt(receipt);          
}      
} 
 }  

通過責任鏈的處理方式,if-else結構也被我們消除了,每當新來了一種回執(zhí),只需要添加IReceiptHandler實現類并修改ReceiptHandlerContainer處理者容器即可,如果要使得程序符合開閉原則,則需要調整ReceiptHandlerContainer中處理者的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandler實現類。

這里使用到了一個反射工具類,用于獲取指定接口的所有實現類

/**  
 * @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 是否在加載完成后執(zhí)行靜態(tài)代碼塊      
 * @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; 
     }  
  }  

接下來改造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; 
     }    
}  

至此,該方案完美符合了開閉原則,如果新增一個回執(zhí)類型,只需要添加一個新的回執(zhí)處理器即可,無需做其它改動。如新加了MT6666的回執(zhí),代碼如下

public class Mt6666ReceiptHandler implements IReceiptHandler {  
      @Override      
public void handleReceipt(Receipt receipt, IReceiptHandleChain handleChain) {         
 if (StringUtils.equals("MT6666",receipt.getType())) {             
 System.out.println("解析報文MT6666:" + receipt.getMessage());         
 }          
//處理不了該回執(zhí)就往下傳遞         
 else {             
 handleChain.handleReceipt(receipt);
          }  
    }
  }  

策略模式+注解

此方案其實和上述沒有太大異同,為了能符合開閉原則,通過自定義注解的方式,標記處理者類,然后反射獲取到該類集合,放到Map容器中,這里不再贅述。

小結

if-else或switch case 這種分支判斷的方式對于分支邏輯不多的簡單業(yè)務,還是直觀高效的。對于業(yè)務復雜,分支邏輯多,采用適當的模式技巧,會讓代碼更加清晰,容易維護,但同時類或方法數量也是倍增的。「我們需要對業(yè)務做好充分分析,避免一上來就設計模式,避免過度設計!」

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容