工廠+策略解決多重if-else

解決多個if-else

參考文獻:https://mp.weixin.qq.com/s/UyiqGjPb9K5f7XwlfsrXBw

在之前文章說到,簡單 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 策略。

代碼:

/**
 * 策略可以理解成一種算法
 */
public interface Strategy {
        //用戶享受折扣
        double comput(double money);

        //用戶類型  1 表示普通用戶  2 表示白銀用戶  3 表示黃金用戶
        Integer getType();
}
/**
 * 普通會員策略
 */
public class OrdinaryStrategy implements Strategy{
    @Override
    public double comput(double money) {
        System.out.println("普通會員。。。享受九折折扣");
        return money * 0.9;
    }

    @Override
    public Integer getType() {
        return UserType.ordinaryStrategy;
    }
}
/**
 * 白銀會員
 */
public class SilverStrategy implements Strategy{

    @Override
    public double comput(double money) {
        System.out.println("白銀會員 享受折扣爲8折");
        return money * 0.8;
    }

    @Override
    public Integer getType() {
        return UserType.silverStrategy;
    }
}
/**
 * 黃金會員
 */
public class GoldStrategy implements Strategy {
    @Override
    public double comput(double money) {
        System.out.println("黃金會員 享受的折扣是 7折");
        return money * 0.7;
    }

    @Override
    public Integer getType() {
        return UserType.GOLD_STRATEGY;
    }
}
public interface   UserType {
    //普通用戶
    public static final Integer ordinaryStrategy = 1;

    //白銀用戶
    public static final Integer silverStrategy = 2;

    //黃金用戶
    public static final Integer GOLD_STRATEGY = 3;


    //白金用戶
}
import java.util.*;
import java.util.stream.Collectors;

/**
 * 策略工廠
 */
public class StrategyFactory {
    private Map<Integer,Strategy> map = new HashMap<>();

    public StrategyFactory(){
        List<Strategy> strategyList = new ArrayList<>();
        strategyList.add(new OrdinaryStrategy());
        strategyList.add(new SilverStrategy());
        strategyList.add(new GoldStrategy());



        map = strategyList.stream().collect(Collectors.toMap(Strategy::getType,strategy -> strategy));

      /*  //遍歷集合。map 的key 是usertype  value:strategy
        for (Strategy strategy : strategyList) {
           // System.out.println(strategy.getType());
            map.put(strategy.getType(),strategy);
        }*/
    }

    //靜態內部類的單利
    public static class Hold{
        public static StrategyFactory install = new StrategyFactory();
    }

    //初始化工廠
    public static StrategyFactory getStrategyFactory(){
        return Hold.install;
    }

    //獲取策略
    public Strategy getType(Integer type){
         return map.get(type);
    }


}
import com.tubang.user.Strategy;
import com.tubang.user.StrategyFactory;

public class TestStrategy {

    public static void main(String[] args) {


        //System.out.println("普通會員購買 100:"+buy(1,2000));
        // System.out.println("白銀會員購買 2000,折扣後的價格:"+buy(2,2000));
         System.out.println("黃金會員購買 2000,折扣後的價格:"+buy(3,2000));


    }

    public static double buy(Integer type, double money){
        if(money <= 1000){
            return money;
        }

        Strategy strategy = StrategyFactory.getStrategyFactory().getType(type);
        if(strategy == null){
            throw new RuntimeException("用戶類型錯誤!!!");
        }
        return strategy.comput(money);
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章