如何優(yōu)雅的替換掉Java代碼中的if else
場(chǎng)景
平時(shí)我們?cè)趯懘a時(shí),需要針對(duì)不同情況處理不同的業(yè)務(wù)邏輯,用得最多的就是if和else。 但是如果情況太多,就會(huì)出現(xiàn)一大堆的“if else”,這就是為什么很多遺留系統(tǒng)中,一個(gè)函數(shù)可能出現(xiàn)上千行的代碼。當(dāng)然你說(shuō)可以通過(guò)抽取方法或者類來(lái)實(shí)現(xiàn),每一個(gè)情況交給一個(gè)方法或者對(duì)應(yīng)一個(gè)類來(lái)處理,但是這樣做只是看起來(lái)代碼整潔了一些,還是有大量的”if else",后面有新的邏輯時(shí),又要添加更多的“if else",沒有從根本上解決問(wèn)題。
舉個(gè)例子,短信發(fā)送業(yè)務(wù)的實(shí)現(xiàn),一般公司會(huì)接入多個(gè)短信供應(yīng)商,比如夢(mèng)網(wǎng)、玄武、阿里云等多個(gè)短信平臺(tái)(我們稱之為短信渠道),可能需要針對(duì)不同的短信類型或者短信平臺(tái)的穩(wěn)定性來(lái)切換短信渠道:
比如阿里云短信管控很嚴(yán),帶營(yíng)銷字樣的短信不讓發(fā)送,則營(yíng)銷類短信需要使用其他短信渠道來(lái)發(fā)送;
也有可能某個(gè)短信平臺(tái)服務(wù)掛了暫時(shí)不可用,需要切換到另一個(gè)短信渠道;
某些短信平臺(tái)有優(yōu)惠,則需要臨時(shí)切換到該短信渠道發(fā)送短信;
…
代碼實(shí)現(xiàn)
上面的業(yè)務(wù)場(chǎng)景簡(jiǎn)單來(lái)說(shuō)就是:針對(duì)不同的短信渠道來(lái)調(diào)用對(duì)應(yīng)的短信平臺(tái)接口實(shí)現(xiàn)短信發(fā)送。
短信渠道一般配置在文件中,或者配置在數(shù)據(jù)庫(kù)中。
代碼實(shí)現(xiàn)如下(注意下面所有的代碼都不能直接運(yùn)行,只是關(guān)鍵邏輯部分的示例代碼):
爛代碼示例
我們有一個(gè)短信發(fā)送類:SmsSendService,里面有一個(gè)send方法發(fā)送短信
SmsSendService.java
public class SmsSendService{ /** * @Param phoneNo 手機(jī)號(hào) * @Param content 短信內(nèi)容 */ public void send(String phoneNo,String content){ //從配置中讀取 短信渠道 String channelType=config.getChannelType(); //如果是短信渠道A,則調(diào)用渠道A的api發(fā)送 if(Objects.equals(channelType,"CHANNEL_A")){ System.out.println("通過(guò)短信渠道A發(fā)送短信"); } //如果是短信渠道B,則調(diào)用渠道B的api發(fā)送 else if(Objects.equals(channelType,"CHANNEL_B")){ System.out.println("通過(guò)短信渠道B發(fā)送短信"); } } }
如果某天增加了一個(gè)短信渠道C,那么接著追加一個(gè)”else if…"
//... 此處省略部分代碼 ... //從配置中讀取 短信渠道 String channelType=config.getChannelType(); //如果是短信渠道A,則調(diào)用渠道A的api發(fā)送 if(Objects.equals(channelType,"CHANNEL_A")){ System.out.println("通過(guò)短信渠道A發(fā)送短信"); } //如果是短信渠道B,則調(diào)用渠道B的api發(fā)送 else if(Objects.equals(channelType,"CHANNEL_B")){ System.out.println("通過(guò)短信渠道B發(fā)送短信"); } //ADD: 如果是短信渠道C,則調(diào)用渠道C的api發(fā)送 else if(Objects.equals(channelType,"CHANNEL_C")){ System.out.println("通過(guò)短信渠道C發(fā)送短信"); } //... 此處省略部分代碼 ...
如果又加其他短信渠道了呢?你又寫一個(gè)“else if …" ?
顯然這種做法不可取,也不符合SOLID原則中的”開閉原則“ ——對(duì)擴(kuò)展開放,對(duì)更改封閉。
這樣我們每次都需要修改原有代碼(對(duì)更改沒有封閉),不斷的添加”if else"。
接下來(lái)我們把代碼優(yōu)化一下:
優(yōu)化代碼1
定義一個(gè)短信渠道的接口 SmsChannelService,所有的短信渠道API都實(shí)現(xiàn)該接口;
短信渠道接口 SmsChannelService.java
public interface SmsChannelService{ //發(fā)送短信 void send(String phoneNo,String content); }
短信渠道A SmsChannelServiceImplA.java
public class SmsChannelServiceImplA implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道A發(fā)送短信"); } }
短信渠道B SmsChannelServiceImplB.java
public class SmsChannelServiceImplB implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道B發(fā)送短信"); } }
通過(guò)工廠類來(lái)初始化所有短信渠道service
SmsChannelFactory.java
public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; //初始化工廠,將所有的短信渠道Service放入Map中 public SmsChannelFactory(){ //渠道類型為 key , 對(duì)應(yīng)的服務(wù)類為value : serviceMap=new HashMap<String, SmsChannelService>(2); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA()); serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); } //根據(jù)短信渠道類型獲得對(duì)應(yīng)渠道的Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
在原來(lái)的SmsSendService中調(diào)用不同短信渠道的接口。
原來(lái)的 SmsSendService 類優(yōu)化如下
public class SmsSendService { private SmsChannelFactory smsChannelFactory; public SmsSendService(){ smsChannelFactory=new SmsChannelFactory(); } public void send(String phoneNo,String content){ //從配置中讀取 短信渠道 String channelType=config.getChannelType(); //獲取渠道類型對(duì)應(yīng)的服務(wù)類 SmsChannelService channelService=smsChannelFactory.buildService(channelType); //發(fā)送短信 channelService.send(phoneNo,content); } }
這樣SmsSendService類非常簡(jiǎn)潔,把“if else"干掉了,
如果我要增加一個(gè)短信渠道C,無(wú)需再次更改 SmsSendService 類。
只需要增加一個(gè)類 SmsChannelServiceImplC 實(shí)現(xiàn) SmsChannelService 接口,
然后在工廠類 SmsChannelFactory 中增加一行初始化 SmsChannelServiceImplC 的代碼即可。
增加短信渠道C的實(shí)現(xiàn) SmsChannelServiceImplC.java
public class SmsChannelServiceImplC implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道C發(fā)送短信"); } }
修改工廠類 SmsChannelFactory.java
public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; //初始化 serviceMap ,將所有的短信渠道Service放入Map中 public SmsChannelFactory(){ //渠道類型為 key , 對(duì)應(yīng)的服務(wù)類為value : serviceMap=new HashMap<String, SmsChannelService>(3); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA()); serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); //ADD 增加一行 SmsChannelServiceImplC 的初始化代碼 serviceMap.put("CHANNEL_C",new SmsChannelServiceImplC()); } //根據(jù)渠道類型構(gòu)建短信渠道Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
“if else"是干掉了,但還是得修改原來(lái)的類 SmsChannelFactory ,不滿足"開閉原則",有沒有更好得方式呢?
我們通過(guò)使用spring的依賴注入進(jìn)一步優(yōu)化代碼:
優(yōu)化代碼2
SmsChannelService 接口增加 getChannelType() 方法,這一步很關(guān)鍵。
public interface SmsChannelService { //發(fā)送短信 void send(String phoneNo,String content); //關(guān)鍵:增加getChannelType()方法,子類實(shí)現(xiàn)這個(gè)方法用于標(biāo)識(shí)出渠道類型 String getChannelType(); }
子類增加該方法的實(shí)現(xiàn),并加上 @Service 注解,使其讓spring容器管理起來(lái)
SmsChannelServiceImplA.java
@Service public class SmsChannelServiceImplA implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道A發(fā)送短信"); } //關(guān)鍵:增加 getChannelType() 實(shí)現(xiàn) public String getChannelType() { return "CHANNEL_A"; } }
SmsChannelServiceImplB.java
@Service public class SmsChannelServiceImplB implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道B發(fā)送短信"); } //關(guān)鍵:增加 getChannelType() 實(shí)現(xiàn) public String getChannelType() { return "CHANNEL_B"; } }
修改 SmsChannelFactory 類: 這一步也很關(guān)鍵。
SmsChannelFactory.java
@Service public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; /*注入:通過(guò)spring容器將所有實(shí)現(xiàn) SmsChannelService 接口的類的實(shí)例注入到 serviceList 中*/ @Autowired private List<SmsChannelService> serviceList; /*通過(guò) @PostConstruct 注解,在 SmsChannelFactory 實(shí)例化后,來(lái)初始化 serviceMap */ @PostConstruct private void init(){ if(CollectionUtils.isEmpty(serviceList)){ return ; } serviceMap=new HashMap<String, SmsChannelService>(serviceList.size()); //將 serviceList 轉(zhuǎn)換為 serviceMap for (SmsChannelService channelService : serviceList) { String channelType=channelService.getChannelType(); //重復(fù)性校驗(yàn),避免不同實(shí)現(xiàn)類的 getChannelType() 方法返回同一個(gè)值。 if(serviceMap.get(channelType)!=null){ throw new RuntimeException("同一個(gè)短信渠道只能有一個(gè)實(shí)現(xiàn)類"); } /*渠道類型為 key , 對(duì)應(yīng)的服務(wù)類為value : 與“優(yōu)化代碼1”中的通過(guò)手工設(shè)置“CHANNEL_A"、"CHANNEL_B"相比, 這種方式更加自動(dòng)化,后續(xù)在增加“CHANNEL_C"無(wú)需再改此處代碼*/ serviceMap.put(channelType,channelService); } } //根據(jù)渠道類型獲取對(duì)應(yīng)短信渠道的Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
SmsSendService 加上 @Service 注解。通過(guò) @Autowired 注入 SmsChannelFactory
SmsSendService.java
@Service public class SmsSendService { @Autowired private SmsChannelFactory smsChannelFactory; public void send(String phoneNo,String content){ //從配置中讀取短信渠道類型 String channelType=config.getChannelType(); //構(gòu)建渠道類型對(duì)應(yīng)的服務(wù)類 SmsChannelService channelService=smsChannelFactory.buildService(channelType); //發(fā)送短信 channelService.send(phoneNo,content); } }
這時(shí),如果需要添加一個(gè)渠道C,那真的只需要添加一個(gè) SmsChannelServiceImplC 即可,再也不用改原有代碼,完全遵循“開閉原則”。
SmsChannelServiceImplC.java
@Service public class SmsChannelServiceImplC implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通過(guò)短信渠道C發(fā)送短信"); } public String getChannelType() { return "CHANNEL_C"; } }
以上就是如何優(yōu)雅的替換掉Java代碼中的if else的詳細(xì)內(nèi)容,更多關(guān)于替換代碼中的if else的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決IDEA刪除子模塊并重建后MAVEN無(wú)法識(shí)別的問(wèn)題
這篇文章主要介紹了解決IDEA刪除子模塊并重建后MAVEN無(wú)法識(shí)別的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02springcloud連接遠(yuǎn)程nacos失敗顯示localhost服務(wù)連接失敗的問(wèn)題解決
這篇文章主要介紹了springcloud連接遠(yuǎn)程nacos失敗顯示localhost服務(wù)連接失敗的問(wèn)題解決,文中有詳細(xì)的代碼示例供大家參考,對(duì)大家解決問(wèn)題有一定的幫助,需要的朋友可以參考下2024-03-03JVM執(zhí)行引擎和垃圾回收要點(diǎn)總結(jié)
不論是在問(wèn)題現(xiàn)場(chǎng)還是跳槽面試,我們面對(duì)JVM性能問(wèn)題,依舊會(huì)束手無(wú)辭,它需要你對(duì)Java虛擬機(jī)的實(shí)現(xiàn)和優(yōu)化,有極為深刻的理解。所以我在這里整理了一下 JVM的知識(shí)點(diǎn)。今天說(shuō)說(shuō)虛擬機(jī)執(zhí)行引擎和垃圾回收,都是十足的干貨,請(qǐng)各位看官耐心批閱!2021-06-06Springboot集成Springbrick實(shí)現(xiàn)動(dòng)態(tài)插件的步驟詳解
這篇文章主要介紹了Springboot集成Springbrick實(shí)現(xiàn)動(dòng)態(tài)插件的詳細(xì)過(guò)程,文中的流程通過(guò)代碼示例介紹的非常詳細(xì),感興趣的同學(xué)可以參考一下2023-06-06Kotlin + Spring Boot 請(qǐng)求參數(shù)驗(yàn)證的代碼實(shí)例
本篇文章主要介紹了Kotlin + Spring Boot 請(qǐng)求參數(shù)驗(yàn)證的代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07java+io+swing實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java+io+swing實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07