深度解析:如何替換掉代碼中的ifelse,我女友看完都會了!

平時咱們在寫代碼時,須要針對不一樣狀況處理不一樣的業務邏輯,用得最多的就是if和else。 可是若是狀況太多,就會出現一大堆的「if else」,這就是爲何不少遺留系統中,一個函數可能出現上千行的代碼。固然你說能夠經過抽取方法或者類來實現,每個狀況交給一個方法或者對應一個類來處理,可是這樣作只是看起來代碼整潔了一些,仍是有大量的」if else",後面有新的邏輯時,又要添加更多的「if else",沒有從根本上解決問題。java

舉個例子,短信發送業務的實現,通常公司會接入多個短信供應商,好比夢網、玄武、阿里雲等多個短信平臺(咱們稱之爲短信渠道),可能須要針對不一樣的短信類型或者短信平臺的穩定性來切換短信渠道:
1.好比阿里雲短信管控很嚴,帶營銷字樣的短信不讓發送,則營銷類短信須要使用其餘短信渠道來發送;
2.也有可能某個短信平臺服務掛了暫時不可用,須要切換到另外一個短信渠道;
3.某些短信平臺有優惠,則須要臨時切換到該短信渠道發送短信;
4.…spring

代碼實現

上面的業務場景簡單來講就是:針對不一樣的短信渠道來調用對應的短信平臺接口實現短信發送。
短信渠道通常配置在文件中,或者配置在數據庫中。數據庫

代碼實現以下(注意下面全部的代碼都不能直接運行,只是關鍵邏輯部分的示例代碼):api

爛代碼示例

咱們有一個短信發送類:SmsSendService,裏面有一個send方法發送短信
SmsSendService.javaide

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"。優化

接下來咱們把代碼優化一下:ui

優化代碼1

定義一個短信渠道的接口 SmsChannelService,全部的短信渠道API都實現該接口;
短信渠道接口 SmsChannelService.java阿里雲

public interface SmsChannelService{
    //發送短信
    void send(String phoneNo,String content);
}

短信渠道A SmsChannelServiceImplA.javacode

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是個好東西,關鍵看你怎麼用。

看完有什麼不懂的歡迎在下方留言評論,記得點個贊哦!

相關文章
相關標籤/搜索