はじめに:偉大な神によって書かれたコードの最初のリリースを開いて、とにかく、私はこの写真を見る最初の反応は賞賛にある(少し残基を描くが、それでも私は、ロジック内の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,塑料成为了其下属,尽管白银对塑料各种嫌弃,一心想让塑料拍屁股走人,但一想到塑料的这段拳皇代码,还是算了...