如何優雅的替換掉程式碼中的ifelse
場景
平時我們在寫程式碼時,需要針對不同情況處理不同的業務邏輯,用得最多的就是if和else。 但是如果情況太多,就會出現一大堆的“if else”,這就是為什麼很多遺留系統中,一個函式可能出現上千行的程式碼。當然你說可以通過抽取方法或者類來實現,每一個情況交給一個方法或者對應一個類來處理,但是這樣做只是看起來程式碼整潔了一些,還是有大量的”if else",後面有新的邏輯時,又要新增更多的“if else",沒有從根本上解決問題。
舉個例子,簡訊傳送業務的實現,一般公司會接入多個簡訊供應商,比如夢網、玄武、阿里雲等多個簡訊平臺(我們稱之為簡訊渠道),可能需要針對不同的簡訊型別或者簡訊平臺的穩定性來切換簡訊渠道:
- 比如阿里雲簡訊管控很嚴,帶營銷字樣的簡訊不讓傳送,則營銷類簡訊需要使用其他簡訊渠道來傳送;
- 也有可能某個簡訊平臺服務掛了暫時不可用,需要切換到另一個簡訊渠道;
- 某些簡訊平臺有優惠,則需要臨時切換到該簡訊渠道傳送簡訊;
- …
程式碼實現
上面的業務場景簡單來說就是:針對不同的簡訊渠道來呼叫對應的簡訊平臺介面實現簡訊傳送。
簡訊渠道一般配置在檔案中,或者配置在資料庫中。
程式碼實現如下(注意下面所有的程式碼都不能直接執行,只是關鍵邏輯部分的示例程式碼):
爛程式碼示例
我們有一個簡訊傳送類:SmsSendService,裡面有一個send方法傳送簡訊
SmsSendService.java
public class SmsSendService{
/**
* @Param phoneNo 手機號
* @Param content 簡訊內容
*/
public void send(String phoneNo,String content){
//從配置中讀取 簡訊渠道
String channelType=config.getChannelType();
//如果是簡訊渠道A,則呼叫渠道A的api傳送
if(Objects.equals(channelType,"CHANNEL_A")){
System.out.println("通過簡訊渠道A傳送簡訊");
}
//如果是簡訊渠道B,則呼叫渠道B的api傳送
else if(Objects.equals(channelType,"CHANNEL_B")){
System.out.println("通過簡訊渠道B傳送簡訊");
}
}
}
如果某天增加了一個簡訊渠道C,那麼接著追加一個”else if…"
//... 此處省略部分程式碼 ...
//從配置中讀取 簡訊渠道
String channelType=config.getChannelType();
//如果是簡訊渠道A,則呼叫渠道A的api傳送
if(Objects.equals(channelType,"CHANNEL_A")){
System.out.println("通過簡訊渠道A傳送簡訊");
}
//如果是簡訊渠道B,則呼叫渠道B的api傳送
else if(Objects.equals(channelType,"CHANNEL_B")){
System.out.println("通過簡訊渠道B傳送簡訊");
}
//ADD: 如果是簡訊渠道C,則呼叫渠道C的api傳送
else if(Objects.equals(channelType,"CHANNEL_C")){
System.out.println("通過簡訊渠道C傳送簡訊");
}
//... 此處省略部分程式碼 ...
如果又加其他簡訊渠道了呢?你又寫一個“else if …" ?
顯然這種做法不可取,也不符合SOLID原則中的”開閉原則“ ——對擴充套件開放,對更改封閉。
這樣我們每次都需要修改原有程式碼(對更改沒有封閉),不斷的新增”if else"。
接下來我們把程式碼優化一下:
優化程式碼1
- 定義一個簡訊渠道的介面 SmsChannelService,所有的簡訊渠道API都實現該介面;
簡訊渠道介面 SmsChannelService.java
public interface SmsChannelService{
//傳送簡訊
void send(String phoneNo,String content);
}
簡訊渠道A SmsChannelServiceImplA.java
public class SmsChannelServiceImplA implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道A傳送簡訊");
}
}
簡訊渠道B SmsChannelServiceImplB.java
public class SmsChannelServiceImplB implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道B傳送簡訊");
}
}
- 通過工廠類來初始化所有簡訊渠道service
SmsChannelFactory.java
public class SmsChannelFactory {
private Map<String,SmsChannelService> serviceMap;
//初始化工廠,將所有的簡訊渠道Service放入Map中
public SmsChannelFactory(){
//渠道型別為 key , 對應的服務類為value :
serviceMap=new HashMap<String, SmsChannelService>(2);
serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
}
//根據簡訊渠道型別獲得對應渠道的Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}
- 在原來的SmsSendService中呼叫不同簡訊渠道的介面。
原來的 SmsSendService
類優化如下
public class SmsSendService {
private SmsChannelFactory smsChannelFactory;
public SmsSendService(){
smsChannelFactory=new SmsChannelFactory();
}
public void send(String phoneNo,String content){
//從配置中讀取 簡訊渠道
String channelType=config.getChannelType();
//獲取渠道型別對應的服務類
SmsChannelService channelService=smsChannelFactory.buildService(channelType);
//傳送簡訊
channelService.send(phoneNo,content);
}
}
這樣SmsSendService類非常簡潔,把“if else"幹掉了,
如果我要增加一個簡訊渠道C,無需再次更改 SmsSendService 類。
只需要增加一個類 SmsChannelServiceImplC 實現 SmsChannelService 介面,
然後在工廠類 SmsChannelFactory 中增加一行初始化 SmsChannelServiceImplC 的程式碼即可。
增加簡訊渠道C的實現 SmsChannelServiceImplC.java
public class SmsChannelServiceImplC implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道C傳送簡訊");
}
}
修改工廠類 SmsChannelFactory.java
public class SmsChannelFactory {
private Map<String,SmsChannelService> serviceMap;
//初始化 serviceMap ,將所有的簡訊渠道Service放入Map中
public SmsChannelFactory(){
//渠道型別為 key , 對應的服務類為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());
}
//根據渠道型別構建簡訊渠道Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}
“if else"是幹掉了,但還是得修改原來的類
SmsChannelFactory
,不滿足"開閉原則",有沒有更好得方式呢?
我們通過使用spring的依賴注入進一步優化程式碼:
優化程式碼2
SmsChannelService
介面增加 getChannelType() 方法,這一步很關鍵。
public interface SmsChannelService {
//傳送簡訊
void send(String phoneNo,String content);
//關鍵:增加getChannelType()方法,子類實現這個方法用於標識出渠道型別
String getChannelType();
}
- 子類增加該方法的實現,並加上 @Service 註解,使其讓spring容器管理起來
SmsChannelServiceImplA.java
@Service
public class SmsChannelServiceImplA implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道A傳送簡訊");
}
//關鍵:增加 getChannelType() 實現
public String getChannelType() {
return "CHANNEL_A";
}
}
SmsChannelServiceImplB.java
@Service
public class SmsChannelServiceImplB implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道B傳送簡訊");
}
//關鍵:增加 getChannelType() 實現
public String getChannelType() {
return "CHANNEL_B";
}
}
- 修改
SmsChannelFactory
類: 這一步也很關鍵。
SmsChannelFactory.java
@Service
public class SmsChannelFactory {
private Map<String,SmsChannelService> serviceMap;
/*注入:通過spring容器將所有實現 SmsChannelService 介面的類的例項注入到 serviceList 中*/
@Autowired
private List<SmsChannelService> serviceList;
/*通過 @PostConstruct 註解,在 SmsChannelFactory 例項化後,來初始化 serviceMap */
@PostConstruct
private void init(){
if(CollectionUtils.isEmpty(serviceList)){
return ;
}
serviceMap=new HashMap<String, SmsChannelService>(serviceList.size());
//將 serviceList 轉換為 serviceMap
for (SmsChannelService channelService : serviceList) {
String channelType=channelService.getChannelType();
//重複性校驗,避免不同實現類的 getChannelType() 方法返回同一個值。
if(serviceMap.get(channelType)!=null){
throw new RuntimeException("同一個簡訊渠道只能有一個實現類");
}
/*渠道型別為 key , 對應的服務類為value :
與“優化程式碼1”中的通過手工設定“CHANNEL_A"、"CHANNEL_B"相比,
這種方式更加自動化,後續在增加“CHANNEL_C"無需再改此處程式碼*/
serviceMap.put(channelType,channelService);
}
}
//根據渠道型別獲取對應簡訊渠道的Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}
- SmsSendService 加上 @Service 註解。通過 @Autowired 注入 SmsChannelFactory
SmsSendService.java
@Service
public class SmsSendService {
@Autowired
private SmsChannelFactory smsChannelFactory;
public void send(String phoneNo,String content){
//從配置中讀取簡訊渠道型別
String channelType=config.getChannelType();
//構建渠道型別對應的服務類
SmsChannelService channelService=smsChannelFactory.buildService(channelType);
//傳送簡訊
channelService.send(phoneNo,content);
}
}
這時,如果需要新增一個渠道C,那真的只需要新增一個 SmsChannelServiceImplC 即可,再也不用改原有程式碼,完全遵循“開閉原則”。
SmsChannelServiceImplC.java
@Service
public class SmsChannelServiceImplC implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通過簡訊渠道C傳送簡訊");
}
public String getChannelType() {
return "CHANNEL_C";
}
}
總結
通過上述優化很好的去掉了 “if else" ,再也不會出現”又臭又長“像”衛生捲紙"一樣的程式碼了,而且完全遵循”開閉原則"。
spring是個好東西,關鍵看你怎麼用。
相關文章
- 如何寫出優雅的程式碼?
- 如何優雅的打包前端程式碼前端
- 優雅的程式碼
- 如何用 SpringBoot 優雅的寫程式碼Spring Boot
- 如何寫出優雅耐看的JavaScript程式碼JavaScript
- 如何提高Java程式碼質量-優雅的寫程式碼Java
- Jquery 替換掉路徑中的某些欄位 replaceAll(selector)jQuery
- golang如何優雅的編寫事務程式碼Golang
- 程式中的敏感資訊如何優雅的處理?
- idea替換內容快捷鍵 idea怎麼替換掉所選的文字Idea
- 如何優雅的消滅掉react生命週期函式React函式
- 我是如何將業務程式碼寫優雅的
- 騷操作:不重啟 JVM,如何替換掉已經載入的類?JVM
- 【原創】如何優雅的轉換Bean物件Bean物件
- 如何優雅阻止view UI 的 Switch 切換?ViewUI
- 寫出優雅的js程式碼JS
- 翻譯 | 怎麼在Java中替換掉繁雜的if語句Java
- 教你如何替換@PathVariable中的變數變數
- 如何將Windows的桌面替換成自己的程式Windows
- 【優雅寫程式碼系統】springboot+mybatis+pagehelper+mybatisplus+druid教你如何優雅寫程式碼Spring BootMyBatisUI
- 如何用 es6+ 寫出優雅的 js 程式碼JS
- 看promise教你如何優雅的寫js非同步程式碼PromiseJS非同步
- React中如何優雅的使用UEditorReact
- 編寫更優雅的 JavaScript 程式碼JavaScript
- 網站程式碼修改替換流程圖,輕鬆掌握程式碼修改替換流程網站流程圖
- 如何優雅地改善程式中for迴圈
- 如何優雅的分析 ThinkPHP 框架原始碼PHP框架原始碼
- Laravel 5.6 中優雅的管理 swoole 程式Laravel
- 如何優雅地管理複雜前端程式碼前端
- js中字串的替換JS字串
- 如何在 React 中優雅的使用 addEventListenerReactdev
- consul系列文章02---替換掉.netcore的配置檔案NetCore
- 如何為分散式系統優雅的更換RPC分散式RPC
- 實戰:如何優雅的從 Skywalking 切換到 OpenTelemetry
- 你的 JS 程式碼本可以更加優雅JS
- 那些優雅靈性的JS程式碼片段JS
- 編寫優雅程式碼的最佳實踐
- 如何在word中進行查詢與替換 word文件中的替換與查詢功能