SpringBootポリシーモードを使用してif elseを殺す方法


シーン:いくつかのデータを受信して処理する必要がある場合、それらは異なるチャネル(例えばテンセント、トップバー)から来ているため、異なるチャネルに必要な処理方法が異なるので、以下に簡単なDemoを書いてこのシーンを実現します.
解決策
例1:簡単な例
1、まずGeneralChannelRule基礎規則抽象クラスを構築し、抽象メソッドprocess()を定義し、異なるチャネルでこの抽象メソッドを実現する必要がある.
public abstract class GeneralChannelRule {
 public abstract void process();
}

2、テンセントのルールクラスを編纂し、テンセントのルートデータに対する処理ロジックを定義する
public class TencentChannelRule extends GeneralChannelRule
   @Override
    public void process() {
        // Tencent    
    }
}

3.ヘッダーのルールクラスを作成し、ヘッダーデータに対する具体的な処理ロジックを定義する
public class TouTiaoChannelRule extends GeneralChannelRule
   @Override
    public void process() {
        // TouTiao    
    }
}

4、簡単な列挙クラスを作る
public enum ChannelRuleEnum {
    /**
     *   
     */
    TOUTIAO("TOUTIAO"),
    /**
     *   
     */
    TENCENT("TENCENT"),
    ;
  ....
}

5、ルールを使用してデータを処理する.
public static void main(String[] args) {
        //            ,     TOUTIAO,       
        String sign = "TOUTIAO";
        GeneralChannelRule rule;
        //                  
        if (ChannelRuleEnum.TENCENT.code.equals(sign)) {
            rule = new TencentChannelRule();
        } else if (ChannelRuleEnum.TOUTIAO.code.equals(sign)) {
            rule = new TouTiaoChannelRule();
        } else {
            //    
        }
        //  
        rule.process();
    }

解析:上記の方法では、2つの欠点があります.
新しいチャネルを追加する必要がある場合は、mainメソッドの論理を修正して調整する必要があります.これは設計モードにおけるオープンクローズ規則に反する.開放閉鎖原則の核心的な思想は、ソフトウェアエンティティが拡張可能であり、修正不可能であることである.
すなわち,拡張に対しては開放的であり,修正に対しては閉鎖的である.
チャネルを追加すると、コードを変更するとif elseが大量に発生し、あまり優雅ではありません.以上の2つの問題を解決するために,列挙クラスを用いて巧みに最適化することができる.
新しい考え方
  • 1、次に列挙クラスを調整し、GeneralChannelRuleプロパティを追加し、対応するチャネルに対応するGeneralChannelRule実装クラスを構築し、match()マッチングメソッドを追加します.
  • public enum ChannelRuleEnum {
    
        /**
         *   
         */
        TOUTIAO("TOUTIAO",new TouTiaoChannelRule()),
        /**
         *   
         */
        TENCENT("TENCENT",new TencentChannelRule()),
        ;
    
        public String name;
    
        public GeneralChannelRule channel;
    
        ChannelRuleEnum(String name, GeneralChannelRule channel) {
            this.name = name;
            this.channel = channel;
        }
    
      //  
        public static ChannelRuleEnum match(String name){
            ChannelRuleEnum[] values = ChannelRuleEnum.values();
            for (ChannelRuleEnum value : values) {
                if(value.name.equals(name)){
                    return value;
                }
            }
            return null;
        }
        public String getName() {
            return name;
        }
    
        public GeneralChannelRule getChannel() {
            return channel;
        }
    }
    
  • 2、書き換えプログラム
  • public static void main(String[] args) {
            String sign = "TOUTIAO";
            ChannelRuleEnum channelRule = ChannelRuleEnum.match(sign);
            GeneralChannelRule rule = channelRule.channel;
            rule.process(sign);
        }
    

    解析:列挙クラスを使用することで、列挙でkeyとルールの具体的な実装をバインドします.変更:
    if-elseを減らすことで、コードをより優雅にすることができます.新しいチャネルが必要な場合は、特定のルール実装クラスを作成し、GeneralChannelRule抽象クラスを継承し、元のコードに変更することなく、列挙クラスに新しい列挙を追加する必要があります.これは開発閉鎖の原則に合致している.
    最後に以上は列挙によってif-elseを巧みに殺す案であり、if-elseを減らすには興味深い解決策(状態設計モードなど)がたくさんあり、興味のある友人は関連資料を調べます.
    例2:SpringBootでのポリシー・モードの運用
    シーン
    ユーザーの注文チャージ(注文支払いと同様)については、支付宝、微信、銀聯、財布(各アプリの口座残高)など、現在の支払い方法が非常に多いことが知られています.クエリーエンティティQuery:
    /**
     * @author Howinfun
     * @desc   
     * @date 2019/10/22
     */
    @Data
    public class ChargeQuery {
        
        /**     (ALI/WX/UNION) */
        @NotBlank(message = "        ",groups = PayWayNotBlank.class)
        private String payWay;
        
        /**      */
        @NotNull(message = "        ",groups = AmountNotNull.class)
        private Double amount;
    }
    
    

    サービスインタフェース:
    /**
     * @author Howinfun
     * @desc   -    
     * @date 2019/10/30
     */
    public interface ChargeRechargeService {
    
        /**
         *                 
         * @param query
         * @return
         */
        Result recharge(ChargeQuery query);
    
        /**
         *     
         * @param rechargeCallBack
         */
        Result rechargeCallBack(RechargeCallBack rechargeCallBack);
    }
    
    

    従来の方法で実現
    if elseまたはswitchを用いて条件判断を行う.
    /**
     * @author Howinfun
     * @desc
     * @date 2019/10/30
     */
    @Service
    @AllArgsConstructor
    @Slf4j
    public class ChargeRechargeServiceImpl implements ChargeRechargeService {
    
        private final CarUserMapper carUserMapper;
        private final IncomePaymentMapper incomePaymentMapper;
        private final RechargeRecordMapper rechargeRecordMapper;
        private final PayWayHandlerContext payWayHandlerContext;
    
    
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Result recharge(ChargeQuery query) {
            Result result = new Result();
            // ......
            // ......
            if (PayConstant.PAY_WAY_WX.equals(query.getPayWay())){
                //   
                // ......
            }else if (PayConstant.PAY_WAY_ALI.equals(query.getPayWay())){
                //    
                // ......
    
            }else if (PayConstant.PAY_WAY_UNION_PAY.equals(query.getPayWay())){
                 //   
                 // ......
        
            }
            return result;
        }
    }
    
    

    まとめ:従来の実装方式は非常に重く、コードは非常に簡潔ではなく、拡張性が悪いことがわかります.新しい支払い方法にアクセスする場合は、else ifを追加し続けるしかありません.
    ポリシー・モード
    Talk is cheap,show me the code.ポリシーモードを使用すると、サービスのコードがどのようになるかを見てみましょう.
    /**
     * @author Howinfun
     * @desc
     * @date 2019/10/30
     */
    @Service
    @AllArgsConstructor
    @Slf4j
    public class ChargeRechargeServiceImpl implements ChargeRechargeService {
    
        private final PayWayHandlerContext payWayHandlerContext;
    
    
        @Override
        @Transactional(rollbackFor = Exception.class)
        public Result recharge(ChargeQuery query) {
            return this.payWayHandlerContext.getHandlerInstance(query.getPayWay()).handler(query);
        }
    }
    
    

    Emmmm、確かに簡単です.コード量が少なく、簡潔になっただけでなく、新しい支払い方法でserviceImplのコードを変更する心配もなくなりました.
    1、まず、支払いタイプに対応するプロセッサを識別するために、注釈をカスタマイズする必要があります.
    /**
     * @author Howinfun
     * @desc      ,      
     * @date 2019/11/2
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface PayWayHandler {
    
        String value();
    }
    
    
  • 2、次に、1つのプロセッサを抽象化し、各支払い方式のプロセッサにこの抽象プロセッサを継承させ、handleメソッドを実現する:
  • /**
     * @author Howinfun
     * @desc          
     * @date 2019/11/2
     */
    public abstract class AbstractPayWayHandler {
    
        /**
         *
         * @param query
         * @return
         */
        abstract public Result handler(ChargeQuery query);
    }
    
    
  • 3、実装クラス、例えば支付宝、微信、銀聯:注意:各プロセッサに@componentを加え、Spring管理に渡す.
  • /**
     * @author Howinfun
     * @desc      
     * @date 2019/11/2
     */
    @Component
    @PayWayHandler("ALI")
    @Slf4j
    @AllArgsConstructor
    public class AliPayWayHandler extends AbstractPayWayHandler {
        
        // ....    
    
        @Override
        public Result handler(ChargeQuery query) {
            Result result = new Result();
            // ......
            return result;
        }
    }
    
    /**
     * @author Howinfun
     * @desc     
     * @date 2019/11/2
     */
    @Component
    @PayWayHandler("WX")
    @Slf4j
    @AllArgsConstructor
    public class WxPayWayHandler extends AbstractPayWayHandler {
        
        // ....    
    
        @Override
        public Result handler(ChargeQuery query) {
            Result result = new Result();
            // ......
            return result;
        }
    }
    
    /**
     * @author Howinfun
     * @desc     
     * @date 2019/11/2
     */
    @Component
    @PayWayHandler("UNION")
    @Slf4j
    @AllArgsConstructor
    public class UnionPayWayHandler extends AbstractPayWayHandler {
        
        // ....    
    
        @Override
        public Result handler(ChargeQuery query) {
            Result result = new Result();
            // ......
            return result;
        }
    }
    
    
  • 4、そして最も重要なのは、クラスを作成し、ApplicationContextAwareインタフェースを実現し、setApplicationContextメソッドを書き換え、カスタム注釈@PayWayHandler付きBeanをスキャンして保存し、Serviceの取得を容易にすることです.
  • /**
     * @author Howinfun
     * @desc
     * @date 2019/11/2
     */
    @Component
    public class PayWayHandlerContext implements ApplicationContextAware {
    
        @Autowired ApplicationContext applicationContext;
    
        /** key PayWay,value class*/
        private static final Map<String,Class> handlerMap = new HashMap<>(10);
    
        public AbstractPayWayHandler getHandlerInstance(String payType){
            Class clazz = handlerMap.get(payType);
            if (clazz == null){
                throw new CustomDeniedException("         ");
            }
            return (AbstractPayWayHandler) applicationContext.getBean(clazz);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            //     PayTypeHandler    
            Map<String,Object> beans = applicationContext.getBeansWithAnnotation(PayWayHandler.class);
            if (beans != null && beans.size() > 0) {
                for (Object serviceBean : beans.values()) {
                    String payType = serviceBean.getClass().getAnnotation(PayWayHandler.class).value();
                    handlerMap.put(payType, serviceBean.getClass());
                }
            }
        }
    }
    
    
    

    まとめ:ここで,ServiceImplは,フロントエンドからのpayWayに応じて対応するhandlerを選択して処理することができる.私はポリシーモデルを利用して複雑なif elseコードを簡略化し、拡張性が大幅に向上し、支払い方式の追加によってビジネスコードを修正する心配がなくなりました.