深さ解析:コードの中のifelseをどのように置き換えるか、私の彼女はすべて見終わった!


普段、コードを書くときに、状況に応じて異なるビジネスロジックを処理する必要があります.最も多く使われているのはifとelseです.しかし、状況が多すぎると、多くの「if else」が現れます.これは、多くの残されたシステムで、1つの関数に数千行のコードが現れる可能性があります.もちろん、抽出方法やクラスで実現することができ、それぞれの状況を1つの方法や対応するクラスに渡して処理することができますが、コードがきれいに見えるだけでなく、「if else」が大量にあり、後に新しい論理がある場合、さらに多くの「if else」を追加し、根本的に問題を解決していません.
例えば、メール送信業務の実現は、夢網、玄武、アリ雲などの複数のメールプラットフォーム(メールチャネルと呼ぶ)に接続されるのが一般的です.例えば、アリクラウドメールの管理が厳しく、マーケティング文字付きのメールは送信させない場合、マーケティング類のメールは他のメールルートを使って送信する必要がある.2.あるメールプラットフォームのサービスが一時的に利用できない可能性があり、別のメールルートに切り替える必要がある.3.一部のメールプラットフォームは优遇があって、临时にこのメールのルートに切り替えてメールを送信する必要があります;4.
コード実装
上記のビジネスシーンは簡単に言えば、異なるメールチャネルに対して対応するメールプラットフォームインタフェースを呼び出してメール送信を実現する.メールチャネルは一般的にファイルに配置されたり、データベースに配置されたりする.
コードは次のように実装されます(次のすべてのコードは、キーロジック部分のサンプルコードにすぎないことに注意してください).
腐ったコードの例
メール送信クラスがあります.SmsSendServicesです.sendメソッドでメールを送信します.SmsSendServices.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
SMSチャネルのインタフェースを定義しますSmsChannelService、すべてのSMSチャネルAPIがインタフェースを実現します.SMSチャネルインタフェースSmsChannelServices.java
public interface SmsChannelService{
    //    
    void send(String phoneNo,String content);
}

SMSチャネルA SmsChannelServiceImplaA.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    ");
    }
}

すべてのメールチャネルを工場クラスで初期化
public class SmsChannelFactory {
    private Map serviceMap;

    //     ,        Service  Map 
    public SmsChannelFactory(){
        //      key ,        value :
        serviceMap=new HashMap(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」をクリアします.SmsSendServiceクラスを再度変更する必要がなく、メールチャネルCを追加する場合は、SmsSendServiceクラスを変更する必要はありません.SmsChannelServiceImplCを追加してSmsChannelServiceインタフェースを実装し、工場クラスSmsChannelFactoryにSmsChannelServiceImplCを初期化するコードを1行追加するだけです.
SMSチャネル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 serviceMap;

    //    serviceMap ,        Service  Map 
    public SmsChannelFactory(){
        //      key ,        value :
        serviceMap=new HashMap(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コンテナを管理します.SmsChannelServiceImplaA.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 serviceMap;

    /*  :  spring        SmsChannelService            serviceList  */
    @Autowired
    private List serviceList;

    /*   @PostConstruct   ,  SmsChannelFactory     ,     serviceMap */
    @PostConstruct
    private void init(){
        if(CollectionUtils.isEmpty(serviceList)){
            return ;
        }
        serviceMap=new HashMap(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注記を追加します.SmsChannelFactorySmsSendService.javaに@Autowiredで注入します.
@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は良いもので、使い方次第です.
何か分からないことがあったら、下にコメントを残して、いいねをクリックしてください.