面試官:項(xiàng)目中你是如何利用設(shè)計(jì)模式,干掉if-else的
前言
物流行業(yè)中,通常會(huì)涉及到EDI報(bào)文(XML格式文件)傳輸和回執(zhí)接收,每發(fā)送一份EDI報(bào)文,后續(xù)都會(huì)收到與之關(guān)聯(lián)的回執(zhí)(標(biāo)識(shí)該數(shù)據(jù)在第三方系統(tǒng)中的流轉(zhuǎn)狀態(tài))。
這里枚舉幾種回執(zhí)類型:MT1101、MT2101、MT4101、MT8104、MT8105、MT9999,系統(tǒng)在收到不同的回執(zhí)報(bào)文后,會(huì)執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯處理。當(dāng)然,實(shí)際業(yè)務(wù)場(chǎng)景并沒有那么籠統(tǒng),這里以回執(zhí)處理為演示案例
模擬一個(gè)回執(zhí)類
@Data
public?class?Receipt?{
????/**
?????*?回執(zhí)信息
?????*/
????String?message;
????/**
?????*?回執(zhí)類型(`MT1101、MT2101、MT4101、MT8104、MT8105、MT9999`)
?????*/
????String?type;
}
模擬一個(gè)回執(zhí)生成器
public?class?ReceiptBuilder?{
????public?static?List?generateReceiptList() {
????????//直接模擬一堆回執(zhí)對(duì)象
????????List?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?receiptList?=?ReceiptBuilder.generateReceiptList();
//循環(huán)處理
for?(Receipt?receipt?:?receiptList)?{
????if?(StringUtils.equals("MT2101",receipt.getType()))?{
????????System.out.println("接收到MT2101回執(zhí)");
????????System.out.println("解析回執(zhí)內(nèi)容");
????????System.out.println("執(zhí)行業(yè)務(wù)邏輯");
????}?else?if?(StringUtils.equals("MT1101",receipt.getType()))?{
????????System.out.println("接收到MT1101回執(zhí)");
????????System.out.println("解析回執(zhí)內(nèi)容");
????????System.out.println("執(zhí)行業(yè)務(wù)邏輯");
????}?else?if?(StringUtils.equals("MT8104",receipt.getType()))?{
????????System.out.println("接收到MT8104回執(zhí)");
????????System.out.println("解析回執(zhí)內(nèi)容");
????????System.out.println("執(zhí)行業(yè)務(wù)邏輯");
????}?else?if?(StringUtils.equals("MT9999",receipt.getType()))?{
????????System.out.println("接收到MT9999回執(zhí)");
????????System.out.println("解析回執(zhí)內(nèi)容");
????????System.out.println("執(zhí)行業(yè)務(wù)邏輯");
????????System.out.println("推送郵件");
????}
????//?......未來可能還有好多個(gè)else?if
}
在遇到if-else的分支業(yè)務(wù)邏輯比較復(fù)雜時(shí),我們都習(xí)慣于將其抽出一個(gè)方法或者封裝成一個(gè)對(duì)象去調(diào)用,這樣整個(gè)if-else結(jié)構(gòu)就不會(huì)顯得太臃腫。就上面例子,當(dāng)回執(zhí)的類型越來越多時(shí),分支else if 就會(huì)越來越多,每增加一個(gè)回執(zhí)類型,就需要修改或添加if-else分支,違反了開閉原則(對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉)
策略模式+Map字典
我們知道, 策略模式的目的是封裝一系列的算法,它們具有共性,可以相互替換,也就是說讓算法獨(dú)立于使用它的客戶端而獨(dú)立變化,客戶端僅僅依賴于策略接口 。在上述場(chǎng)景中,我們可以把if-else分支的業(yè)務(wù)邏輯抽取為各種策略,但是不可避免的是依然需要客戶端寫一些if-else進(jìn)行策略選擇的邏輯,我們可以將這段邏輯抽取到工廠類中去,這就是策略模式+簡(jiǎn)單工廠,代碼如下
策略接口
/**
?*?@Description:?回執(zhí)處理策略接口
?*?@Auther:?wuzhazha
?*/
public?interface?IReceiptHandleStrategy?{
????void?handleReceipt(Receipt?receipt);
}
策略接口實(shí)現(xiàn)類,也就是具體的處理者
public?class?Mt2101ReceiptHandleStrategy?implements?IReceiptHandleStrategy?{
????@Override
????public?void?handleReceipt(Receipt?receipt)?{
????????System.out.println("解析報(bào)文MT2101:"?+?receipt.getMessage());
????}
}
public?class?Mt1101ReceiptHandleStrategy?implements?IReceiptHandleStrategy?{
????@Override
????public?void?handleReceipt(Receipt?receipt)?{
????????System.out.println("解析報(bào)文MT1101:"?+?receipt.getMessage());
????}
}
public?class?Mt8104ReceiptHandleStrategy?implements?IReceiptHandleStrategy?{
????@Override
????public?void?handleReceipt(Receipt?receipt)?{
????????System.out.println("解析報(bào)文MT8104:"?+?receipt.getMessage());
????}
}
public?class?Mt9999ReceiptHandleStrategy?implements?IReceiptHandleStrategy?{
????@Override
????public?void?handleReceipt(Receipt?receipt)?{
????????System.out.println("解析報(bào)文MT9999:"?+?receipt.getMessage());
????}
}
策略上下文類(策略接口的持有者)
/**
?*?@Description:?上下文類,持有策略接口
?*?@Auther:?wuzhazha
?*/
public?class?ReceiptStrategyContext?{
????private?IReceiptHandleStrategy?receiptHandleStrategy;
????/**
?????*?設(shè)置策略接口
?????*?@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?receiptList?=?ReceiptBuilder.generateReceiptList();
????????//策略上下文
????????ReceiptStrategyContext?receiptStrategyContext?=?new?ReceiptStrategyContext();
????????for?(Receipt?receipt?:?receiptList)?{
????????????//獲取并設(shè)置策略
????????????IReceiptHandleStrategy?receiptHandleStrategy?=?ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
????????????receiptStrategyContext.setReceiptHandleStrategy(receiptHandleStrategy);
????????????//執(zhí)行策略
????????????receiptStrategyContext.handleReceipt(receipt);
????????}
????}
}
解析報(bào)文MT2101:我是MT2101回執(zhí)報(bào)文喔
解析報(bào)文MT8104:我是MT8104回執(zhí)報(bào)文喔
由于我們的目的是消除if-else,那么這里需要將ReceiptHandleStrategyFactory策略工廠進(jìn)行改造下,采用字典的方式存放我的策略,而Map具備key-value結(jié)構(gòu),采用Map是個(gè)不錯(cuò)選擇。稍微改造下,代碼如下
/**
?*?@Description:?策略工廠
?*?@Auther:?wuzhazha
?*/
public?class?ReceiptHandleStrategyFactory?{
????private?static?Map?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);
????}
}
經(jīng)過對(duì)策略模式+簡(jiǎn)單工廠方案的改造,我們已經(jīng)消除了if-else的結(jié)構(gòu),每當(dāng)新來了一種回執(zhí),只需要添加新的回執(zhí)處理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合開閉原則,則需要調(diào)整ReceiptHandleStrategyFactory中處理策略的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandleStrategy實(shí)現(xiàn)類,然后放到字典Map中去。學(xué)習(xí)資料:Java進(jìn)階視頻資源
責(zé)任鏈模式
責(zé)任鏈模式是一種對(duì)象的行為模式。在責(zé)任鏈模式里,很多對(duì)象由每一個(gè)對(duì)象對(duì)其下家的引用而連接起來形成一條鏈。請(qǐng)求在這個(gè)鏈上傳遞,直到鏈上的某一個(gè)對(duì)象決定處理此請(qǐng)求。發(fā)出這個(gè)請(qǐng)求的客戶端并不知道鏈上的哪一個(gè)對(duì)象最終處理這個(gè)請(qǐng)求,這使得系統(tǒng)可以在不影響客戶端的情況下動(dòng)態(tài)地重新組織和分配責(zé)任
回執(zhí)處理者接口
/**
?*?@Description:?抽象回執(zhí)處理者接口
?*?@Auther:?wuzhazha
?*/
public?interface?IReceiptHandler?{
????void?handleReceipt(Receipt?receipt,IReceiptHandleChain?handleChain);
}
責(zé)任鏈接口
/**
?*?@Description:?責(zé)任鏈接口
?*?@Auther:?wuzhazha
?*/
public?interface?IReceiptHandleChain?{
????void?handleReceipt(Receipt?receipt);
}
責(zé)任鏈接口實(shí)現(xiàn)類
/**
?*?@Description:?責(zé)任鏈實(shí)現(xiàn)類
?*?@Auther:?wuzhazha
?*/
public?class?ReceiptHandleChain?implements?IReceiptHandleChain?{
?//記錄當(dāng)前處理者位置
????private?int?index?=?0;
????//處理者集合
????private?static?List?receiptHandlerList;
????static?{
????????//從容器中獲取處理器對(duì)象
????????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("解析報(bào)文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("解析報(bào)文MT8104:"?+?receipt.getMessage());
????????}
????????//處理不了該回執(zhí)就往下傳遞
????????else?{
????????????handleChain.handleReceipt(receipt);
????????}
????}
}
責(zé)任鏈處理者容器(如果采用spring,則可以通過依賴注入的方式獲取到IReceiptHandler的子類對(duì)象)
/**
?*?@Description:?處理者容器
?*?@Auther:?wuzhazha
?*/
public?class?ReceiptHandlerContainer?{
????private?ReceiptHandlerContainer(){}
????public?static?List?getReceiptHandlerList() {
????????List?receiptHandlerList?=?new?ArrayList<>();
????????receiptHandlerList.add(new?Mt2101ReceiptHandler());
????????receiptHandlerList.add(new?Mt8104ReceiptHandler());
????????return?receiptHandlerList;
????}
}
客戶端
public?class?Client?{
????public?static?void?main(String[]?args)?{
????????//模擬回執(zhí)
????????List?receiptList?=?ReceiptBuilder.generateReceiptList();
????????for?(Receipt?receipt?:?receiptList)?{
????????????//回執(zhí)處理鏈對(duì)象
????????????ReceiptHandleChain?receiptHandleChain?=?new?ReceiptHandleChain();
????????????receiptHandleChain.handleReceipt(receipt);
????????}
????}
}
解析報(bào)文MT2101:我是MT2101回執(zhí)報(bào)文喔
解析報(bào)文MT8104:我是MT8104回執(zhí)報(bào)文喔
通過責(zé)任鏈的處理方式,if-else結(jié)構(gòu)也被我們消除了,每當(dāng)新來了一種回執(zhí),只需要添加IReceiptHandler實(shí)現(xiàn)類并修改ReceiptHandlerContainer處理者容器即可,如果要使得程序符合開閉原則,則需要調(diào)整ReceiptHandlerContainer中處理者的獲取方式,通過反射的方式,獲取指定包下的所有IReceiptHandler實(shí)現(xiàn)類。學(xué)習(xí)資料:Java進(jìn)階視頻資源
這里使用到了一個(gè)反射工具類,用于獲取指定接口的所有實(shí)現(xiàn)類
/**
?*?@Description:?反射工具類
?*?@Auther:?wuzhazha
?*/
public?class?ReflectionUtil?{
????
????/**
?????*?定義類集合(用于存放所有加載的類)
?????*/
????private?static?final?Set>?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>?getClassSet(String?packageName)?{
????????Set>?classSet?=?new?HashSet<>();
????????try?{
????????????Enumeration?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?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>?classSet,?String?className) ?{
????????Class>?cls?=?loadClass(className,false);
????????classSet.add(cls);
????}
????private?static?void?addClass(Set>?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>?getClassSet()?{
????????return?CLASS_SET;
????}
????/**
?????*?獲取應(yīng)用包名下某父類(或接口)的所有子類(或?qū)崿F(xiàn)類)
?????*?@param?superClass
?????*?@return
?????*/
????public?static?Set>?getClassSetBySuper(Class>?superClass)?{
????????Set>?classSet?=?new?HashSet<>();
????????for?(Class>?cls?:?CLASS_SET)?{
????????????if?(superClass.isAssignableFrom(cls)?&&?!superClass.equals(cls))?{
????????????????classSet.add(cls);
????????????}
????????}
????????return?classSet;
????}
????/**
?????*?獲取應(yīng)用包名下帶有某注解的類
?????*?@param?annotationClass
?????*?@return
?????*/
????public?static?Set>?getClassSetByAnnotation(Class?extends?Annotation>?annotationClass)?{
????????Set>?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?getReceiptHandlerList() {
????????List?receiptHandlerList?=?new?ArrayList<>();
????????//獲取IReceiptHandler接口的實(shí)現(xiàn)類
????????Set>?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;
????}
}
至此,該方案完美符合了開閉原則,如果新增一個(gè)回執(zhí)類型,只需要添加一個(gè)新的回執(zhí)處理器即可,無需做其它改動(dòng)。如新加了MT6666的回執(zhí),代碼如下
public?class?Mt6666ReceiptHandler?implements?IReceiptHandler?{
????@Override
????public?void?handleReceipt(Receipt?receipt,?IReceiptHandleChain?handleChain)?{
????????if?(StringUtils.equals("MT6666",receipt.getType()))?{
????????????System.out.println("解析報(bào)文MT6666:"?+?receipt.getMessage());
????????}
????????//處理不了該回執(zhí)就往下傳遞
????????else?{
????????????handleChain.handleReceipt(receipt);
????????}
????}
}
策略模式+注解
此方案其實(shí)和上述沒有太大異同,為了能符合開閉原則,通過自定義注解的方式,標(biāo)記處理者類,然后反射獲取到該類集合,放到Map容器中,這里不再贅述
小結(jié)
if-else或switch case 這種分支判斷的方式對(duì)于分支邏輯不多的簡(jiǎn)單業(yè)務(wù),還是直觀高效的。對(duì)于業(yè)務(wù)復(fù)雜,分支邏輯多,采用適當(dāng)?shù)哪J郊记桑瑫?huì)讓代碼更加清晰,容易維護(hù),但同時(shí)類或方法數(shù)量也是倍增的。我們需要對(duì)業(yè)務(wù)做好充分分析,避免一上來就設(shè)計(jì)模式,避免過度設(shè)計(jì)!
來源:cnblogs.com/DiDi516/p/11787257.html
程序汪資料鏈接
程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理
Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 06版
堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階
臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!
臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!
字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!
歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈!
