前言
在之前文章說到,簡單 if-else,可以使用 衛語句 進行優化。但是在實際開發中,往往不是簡單 if-else 結構,我們通常會不經意間寫下如下代碼:
-------------------- 理想中的 if-else -------------------- public void today() { if (isWeekend()) { System.out.println("玩游戲"); } else { System.out.println("上班!"); } }
------------------- 現實中的 if-else -------------------- if (money >= 1000) { if (type == UserType.SILVER_VIP.getCode()) { System.out.println("白銀會員 優惠50元"); result = money - 50; } else if (type == UserType.GOLD_VIP.getCode()) { System.out.println("黃金會員 8折"); result = money * 0.8; } else if (type == UserType.PLATINUM_VIP.getCode()) { System.out.println("白金會員 優惠50元,再打7折"); result = (money - 50) * 0.7; } else { System.out.println("普通會員 不打折"); result = money; } } //省略 n 個 if-else ......
毫不誇張的說,我們都寫過類似的代碼,回想起被 if-else 支配的恐懼,我們常常無所下手,甚至不了了之。
下面分享一下我在開發中遇到復雜的 if-else 語句“優雅處理”思路。如有不妥,歡迎大家一起交流學習。
需求
假設有這么一個需求:
一個電商系統,當用戶消費滿1000 金額,可以根據用戶VIP等級,享受打折優惠。
根據用戶VIP等級,計算出用戶最終的費用。
-
普通會員 不打折
-
白銀會員 優惠50元
-
黃金會員 8折
-
白金會員 優惠50元,再打7折
編碼實現
private static double getResult(long money, int type) { double result = money; if (money >= 1000) { if (type == UserType.SILVER_VIP.getCode()) { System.out.println("白銀會員 優惠50元"); result = money - 50; } else if (type == UserType.GOLD_VIP.getCode()) { System.out.println("黃金會員 8折"); result = money * 0.8; } else if (type == UserType.PLATINUM_VIP.getCode()) { System.out.println("白金會員 優惠50元,再打7折"); result = (money - 50) * 0.7; } else { System.out.println("普通會員 不打折"); result = money; } } return result; }
為了方便演示,代碼上我進行了簡單實現,但實際上 if - else 會進行復雜的邏輯計費。從功能上來說,基本完成,但是對於我這種有代碼潔癖的人來說,代碼質量上不忍直視。我們開始着手 優化一下我們的第一版代碼吧。
思考
看到如上代碼,聰明的朋友首先想到的是,這不是典型的策略模式嗎?
你可真是個機靈鬼,我們先嘗試用策略模式來優化一下代碼吧。
策略模式
什么是策略模式?
可能有的朋友還不清楚,什么是策略模式。策略模式是定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。
比如上述需求,有返利、有打折、有折上折等等。這些算法本身就是一種策略。並且這些算法可以相互替換的,比如今天我想讓 白銀會員優惠50,明天可以替換為 白銀會員打9折。
說了那么多,不如編碼來得實在。
編碼
public interface Strategy { // 計費方法 double compute(long money); } // 普通會員策略 public class OrdinaryStrategy implements Strategy { @Override public double compute(long money) { System.out.println("普通會員 不打折"); return money; } } // 白銀會員策略 public class SilverStrategy implements Strategy { @Override public double compute(long money) { System.out.println("白銀會員 優惠50元"); return money - 50; } } // 黃金會員策略 public class GoldStrategy implements Strategy{ @Override public double compute(long money) { System.out.println("黃金會員 8折"); return money * 0.8; } } // 白金會員策略 public class PlatinumStrategy implements Strategy { @Override public double compute(long money) { System.out.println("白金會員 優惠50元,再打7折"); return (money - 50) * 0.7; } }
我們定義來一個 Strategy 接口,並且定義 四個子類,實現接口。在對應的 compute方法 實現自身策略的計費邏輯。
private static double getResult(long money, int type) { double result = money; if (money >= 1000) { if (type == UserType.SILVER_VIP.getCode()) { result = new SilverStrategy().compute(money); } else if (type == UserType.GOLD_VIP.getCode()) { result = new GoldStrategy().compute(money); } else if (type == UserType.PLATINUM_VIP.getCode()) { result = new PlatinumStrategy().compute(money); } else { result = new OrdinaryStrategy().compute(money); } } return result; }
然后對應 getResult 方法,根據 type 替換為對應的 用戶VIP 策略。這里代碼上出現了重復的調用 compute ,我們可以嘗試進一步優化。
private static double getResult(long money, int type) { if (money < 1000) { return money; } Strategy strategy; if (type == UserType.SILVER_VIP.getCode()) { strategy = new SilverStrategy(); } else if (type == UserType.GOLD_VIP.getCode()) { strategy = new GoldStrategy(); } else if (type == UserType.PLATINUM_VIP.getCode()) { strategy = new PlatinumStrategy(); } else { strategy = new OrdinaryStrategy(); } return strategy.compute(money); }
還記得我在第一篇中說到的衛語句嗎?我們在這里把 money < 1000 的情況提前 return。更關注於滿1000邏輯 ,也可以減少不必要的縮進。
深思
我曾一度 以為 策略模式不過如此。以為代碼優化到這已經可以了。
但是還有一個恐怖的事情,if-else 依然存在 :)
我嘗試翻閱了許多書籍,查看如何消除 策略模式中的 if-else
書中大部分的方法是,使用簡單工廠 + 策略模式。把 if - else 切換為 switch 創建一個工廠方法而已。
但是這遠遠沒有達到我想要的效果,打倒 if - else
直到某一天夜里,我大佬在群里分享一個 Java8 小技巧時,從此打開新世界。
工廠 + 策略
public interface Strategy { double compute(long money); // 返回 type int getType(); } public class OrdinaryStrategy implements Strategy { @Override public double compute(long money) { System.out.println("普通會員 不打折"); return money; } // 添加 type 返回 @Override public int getType() { return UserType.SILVER_VIP.getCode(); } } public class SilverStrategy implements Strategy { @Override public double compute(long money) { System.out.println("白銀會員 優惠50元"); return money - 50; } // type 返回 @Override public int getType() { return UserType.SILVER_VIP.getCode(); } } ....省略剩下 Strategy
我們先在 Strategy 新增一個 getType 方法,用來表示 該策略的 type 值。代碼相對簡單,這里就不過多介紹了
public class StrategyFactory { private Map<Integer, Strategy> map; public StrategyFactory() { List<Strategy> strategies = new ArrayList<>(); strategies.add(new OrdinaryStrategy()); strategies.add(new SilverStrategy()); strategies.add(new GoldStrategy()); strategies.add(new PlatinumStrategy()); strategies.add(new PlatinumStrategy()); // 看這里 看這里 看這里! map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy)); /* 等同上面 map = new HashMap<>(); for (Strategy strategy : strategies) { map.put(strategy.getType(), strategy); }*/ } public static class Holder { public static StrategyFactory instance = new StrategyFactory(); } public static StrategyFactory getInstance() { return Holder.instance; } public Strategy get(Integer type) { return map.get(type); } }
靜態內部類單例,單例模式實現的一種,不是本文重點,如不了解,可以自行 google
我們再着手創建一個 StrategyFactory 工廠類。StrategyFactory 這里我使用的是靜態內部類單例,在構造方法的時候,初始化好 需要的 Strategy,並把 list 轉化為 map。
這里 轉化就是“靈魂”所在。
toMap
我們先來看看 Java8 語法中的小技巧。
通常情況下,我們遍歷 List,手動put到 Map 中。
-------------- before ----------------- map = new HashMap<>(); for (Strategy strategy : strategies) { map.put(strategy.getType(), strategy); } -------------- after Java8 ----------------- map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
toMap 第一個參數是一個Function,對應 Map 中的 key,第二個參數也是一個Function,strategy -> strategy, 左邊strategy 是遍歷 strategies 中的每一個strategy,右邊strategy則是 Map 對應 value 值。
若是不了解 Java8 語法的朋友,強烈建議看 《Java8 實戰》,書中詳細的介紹了 Lambda表達式、Stream等語法。
效果
private static double getResult(long money, int type) { if (money < 1000) { return money; } Strategy strategy = StrategyFactory.getInstance().get(type); if (strategy == null){ throw new IllegalArgumentException("please input right type"); } return strategy.compute(money); }
至此,通過一個工廠類,在我們在 getResult()調用的時候,根據傳入 type,即可獲取到 對應 Strategy
再也沒有可怕的 if-else 語句。
完結撒花撒花 : )
后續
后續代碼優化上,若是 Java 項目,可以嘗試使用自定義注解,注解 Strategy 實現類。
這樣可以簡化原來需在工廠類 List 添加一個 Stratey 策略。
最后
以上就是我在開發中遇到復雜的 if-else 語句“優雅處理”思路,如有不妥,歡迎大家一起交流學習。