[コード品質] - どのようにIF-else文を書くためにあまりにも多くのを避けるために、農業の再建はどのようにブロンズからダイヤモンドのコードにありますか?

はじめに:偉大な神によって書かれたコードの最初のリリースを開いて、とにかく、私はこの写真を見る最初の反応は賞賛にある(少し残基を描くが、それでも私は、ロジック内の4Kブルーレイを読み取ることができません)

あなたは、開発中のコードのようにAの作品を書く場合には、お祝いの言葉は、あなたが安全な仕事を持っている、とさえテクニカルディレクターはあなたを得ることはありません、あなたはこのコードを保護するために起こっている行っていますか?

すべてのif-elseの数とレベルを制御するためのif-else書き込みコードの多数を避ける方法を最後に、その後、脇冗談?

アリババJava技術開発のマニュアルは、すでに答えを与えている、ここで私は、同じ時間に加えて、実際のシーンや2アウトそれ以上の並べ替えを行って、その後、いくつかの代替ソリューションを提供します。


ビジネスシナリオを初めて目には: VIPユーザーはVIP割引レベルに応じて課金される料金システムに存在、などユーザーとして別のユーザーID、ごとに異なる課金ロジックは、通常のユーザーは、その後、全額を請求されている、ユーザーがされ評価も高く、より大きな割引努力。(具体的な課金ロジック私には、このように単純化された、理解しやすい、実際はもっと複雑です)

達成した場合は、他の上記のシナリオは、従来使用している場合:

/**
 * 账单
 */
@Data
@AllArgsConstructor
public class Bill {
    /**
     * 账单类型 0:普通用户账单,1:vip1级账单,2:vip2级账单...
     */
    private Integer type;
    /**
     * 账单总金额
     */
    private BigDecimal total;
}
public class BadExample {
    public void calcTotal(Bill bill){
        if (Objects.equals(bill.getTotal(),0)){
            //TODO 普通账单计费逻辑
        }else if (Objects.equals(bill.getTotal(),1)){
            //TODO vip1级用户账单计费逻辑
        }else if (Objects.equals(bill.getTotal(),2)){
            //TODO vip2级用户账单计费逻辑
        }else if (Objects.equals(bill.getTotal(),3)){
            //TODO vip3级用户账单计费逻辑
        }
        //...
    }
}

そこのロジックは非常に各セクション内のロジックを課金、課金を複雑化される場合、コードは、のif-elseの多くになりますまた、場合、それ以外の層の多くが含まれているコードのこの作品を見てみると、乱雑、維持することは困難で、この時間、私たちになります私たちは、コードを最適化しようとすることができます。

ここでは、最適化するために、ブロンズ、シルバー、ゴールド、ダイヤモンド...ダンコード農家を見てどのように取ります。

ブロンズ:

まず全体の列挙クラスは、ケースには、その後、各ロジックのための課金方法をパッケージ化、場合、スイッチケースに交換(一部の偶数の整数の列挙クラスは、直接戦闘マナ0,1,2,3 ...ではありません)コール、コードは一見優雅ないくつかの処理を行い、その効果が制限されており、開閉の原則に準拠していない、いつでも新しい課金ロジック、我々はスイッチに追加し、クラスはまた、新しいタイプを追加列挙する必要がありますので。このコードブロックは比較的簡単ですが、私は自分の脳を作る、書いていません。

シルバー:

課金インタフェースを書きます:

public interface CalcService {
    /**
     * 处理账单计算
     */
    void handleCalc(BigDecimal total);
}

その後、別の実装クラスと異なる課金ロジックは、このインタフェースを実装します:

public class OridinaryCalcServiceImpl implements CalcService {
    @Override
    public void handleCalc(BigDecimal total) {
        System.out.println("处理普通用户的账单计费:" + total);
    }
}
public class Vip1CalcServiceImpl implements CalcService {
    @Override
    public void handleCalc(BigDecimal total) {
        System.out.println("处理vip1级用户的账单计费:"+total);
    }
}

...

然后把这些实现类统一用一个Map<Integer,CalcService>封装起来,在调用时根据key(账单类型Integer)来get对应的service,就能优雅的调用具体的实现类了,不错,这样代码确实优雅了很多,但一旦有新的计费逻辑出现,开发人员不得不去维护这个Map,同样违背了代码设计的开闭原则.

黄金:

黄金的实现思路与白银的类似,只不过黄金平时比较喜欢装X,喜欢在代码里搞点高逼格的东西,利用Java8提供的Function函数式编程,来让自己的代码逼格看起来高一点?话不多说,先上代码后解释:

public class CalcFunction {
    private String calcOrdinary(Bill bill) {
        return "处理普通用户的账单计费:" + bill.getTotal();
    }

    private String calcVip1(Bill bill) {
        return "处理vip1级用户的账单计费:" + bill.getTotal();
    }

    private String calcVip2(Bill bill) {
        return "处理vip2级用户的账单计费:" + bill.getTotal();
    }

    public Function<Bill, String> getFunction(Integer type) {
        Function<Bill, String> ordinary = bill -> calcOrdinary(bill);
        Function<Bill, String> vip1 = bill -> calcVip1(bill);
        Function<Bill, String> vip2 = bill -> calcVip2(bill);
        Supplier<Map<Integer, Function<Bill, String>>> supplier = () -> {
            Map<Integer, Function<Bill, String>> map = new HashMap<>(3);
            map.put(BillType.ORDINARY.getType(), ordinary);
            map.put(BillType.VIP1.getType(), vip1);
            map.put(BillType.VIP2.getType(), vip2);
            return map;
        };
        return supplier.get().get(type);
    }
}
    @Test
    public void testFunctional() {
        CalcFunction calcFunction = new CalcFunction();

        Bill bill1 = new Bill(BillTypeEnum.ORDINARY.getType(), new BigDecimal(500));
        calcFunction.getFunction(bill1.getType()).apply(bill1);

        Bill bill2 = new Bill(BillTypeEnum.VIP1.getType(), new BigDecimal(400));
        calcFunction.getFunction(bill2.getType()).apply(bill2);

        Bill bill3 = new Bill(BillTypeEnum.VIP2.getType(), new BigDecimal(300));
        calcFunction.getFunction(bill3.getType()).apply(bill3);
    }

具体实现思路就是,先按照不同计费规则封装具体的计费方法,然后把方法通过Supplier<?>的get提供给调用方,内部也维护了一个Map用来存放不同的function,黄金的实现看上去也挺优雅的,但还是不满足开闭原则,所以阿里的解决方案里并没有提到这几种方式.

相比白银,黄金的实现方式比较装X,但也不全是装X,通过function接口提供的compose,andThen方法可以灵活地加入其它处理逻辑,所以勉强给评个黄金段位.

钻石:

钻石的思路其实就是策略模式+自定义注解+Springboot监听.

先定义一个统一的计算账单接口,然后不同的计费规则分别去实现此接口,不同的是,钻石采用了自定义注解+Spring监听的模式,在Spring上下文环境发生变化时(一般是Spring项目启动完成),将这些加了注解的实现类统一自动注入到自定义的上下文中,交由Spring来管理,不需要再手动维护.需要时可以直接从上下文中取出对应的处理逻辑,看不懂没关系,不妨先看代码:

①定义统一的账单处理接口

public interface CalcService {
    /**
     * 处理账单计算
     */
    void handleCalc(BigDecimal total);
}

② 不同的计费逻辑分别去实现该接口中的handleCalc方法:

@BillTypeHandler(BillType.ORDINARY)
public class OridinaryCalcServiceImpl implements CalcService {
    @Override
    public void handleCalc(BigDecimal total) {
        System.out.println("处理普通用户的账单计费:" + total);
    }
}
@BillTypeHandler(BillType.VIP1)
public class Vip1CalcServiceImpl implements CalcService {
    @Override
    public void handleCalc(BigDecimal total) {
        System.out.println("处理vip1级用户的账单计费:"+total);
    }
}

省略vip3,vip4...代码,与上面雷同

③定义自定义注解:

@Service
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BillTypeHandler {
    BillType value();

    enum BillType {
        ORDINARY(0, "普通用户"),
        VIP1(1, "一级会员"),
        VIP2(2, "二级会员");
        private Integer type;
        private String desc;

        BillType(Integer type, String desc) {
            this.type = type;
            this.desc = desc;
        }

        public Integer getType() {
            return type;
        }
    }
}

④创建自定义的上下文环境:

此上下文环境中维护了一个Map,用来存放CalcService的实现类,然后通过@Component交由Spring容器管理

@Component
public class BillServiceContext {
    @Getter
    private final static Map<Integer, CalcService> calcServiceMap;

    static {
        calcServiceMap = new HashMap<>();
    }

    public CalcService get(Integer type) {
        return calcServiceMap.get(type);
    }

    public void put(Integer type, CalcService calcService) {
        calcServiceMap.put(type, calcService);
    }
}

⑤定义Springboot的监听器:

在项目启动后用来处理自定义注解的逻辑,先拿到每个添加了自定义注解的类,然后通过反射拿到该类的自定义注解,再通过自定义注解中的值(计费类型type枚举值),然后把该值put进④中自定义的那个上下文环境的Map中

@Component
public class BillTypeListener implements ApplicationListener<ContextRefreshedEvent> {
    @Resource
    private BillServiceContext billServiceContext;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        Map<String, CalcService> beans = contextRefreshedEvent.getApplicationContext().getBeansOfType(
            CalcService.class);
        beans.forEach((k, calcService) -> {
            Class clazz = calcService.getClass();
            BillTypeHandler billTypeHandler = (BillTypeHandler)clazz.getAnnotation(BillTypeHandler.class);
            billServiceContext.put(billTypeHandler.value().getType(), calcService);
        });
    }
}

⑥在需要调用的地方通过@Resource或者@Autowired注解将自定义的上下文环境注入,然后直接调用即可:

@SpringBootTest
@RunWith(SpringRunner.class)
public class BillTest {
    @Autowired
    BillServiceContext billServiceContext;

    @Test
    public void test() {
        Bill bill1 = new Bill(BillType.ORDINARY.getType(), new BigDecimal(500));
        billServiceContext.get(bill1.getType()).handleCalc(bill1.getTotal());

        Bill bill2 = new Bill(BillType.VIP1.getType(), new BigDecimal(400));
        billServiceContext.get(bill2.getType()).handleCalc(bill2.getTotal());

        Bill bill3 = new Bill(BillType.VIP2.getType(), new BigDecimal(300));
        billServiceContext.get(bill3.getType()).handleCalc(bill3.getTotal());
    }
}

测试结果符合预期:

这样的代码看着要比if-else清爽多了,而且在之后如果有新的计费逻辑进来,只需要新增实现类即可,无需改动原有代码,符合开闭原则,调用也是十分简单,后期维护会更方便.


白银:这样的代码不香吗?

塑料:真香!但我还是选择用If-else,不整这些花里胡哨5556的...

若干年过去了,白银当上了CTO,塑料成为了其下属,尽管白银对塑料各种嫌弃,一心想让塑料拍屁股走人,但一想到塑料的这段拳皇代码,还是算了...

发布了89 篇原创文章 · 获赞 69 · 访问量 4万+

おすすめ

転載: blog.csdn.net/lovexiaotaozi/article/details/103910776