[解鎖新姿勢] 回想起被 `if-else` 支配的恐懼,我們要打倒 if - else
前言
[解鎖新姿勢] 兄dei,你程式碼需要優化了
在之前文章說到,簡單 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);
}
}
複製程式碼
靜態內部類單例,單例模式實現的一種,不是本文重點,如不瞭解,可以自行
我們再著手建立一個 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
語句“優雅處理”
思路,如有不妥,歡迎大家一起交流學習。