­

略懂設計模式之策略模式

一個陽光明媚的上午,靚仔正在開心的划水摸魚,耳機里傳來音樂「不是吧不是吧,難道單壓也算壓……」

產品經理突然出現在身後,拍了拍我的肩膀

產品經理:又在摸魚,看來工作不飽和啊,正好有個需求你做一下。公司推出了會員制度,分普通會員和超級會員,普通會員購買商品打九折,超級會員購買商品打八折。

靚仔:就這?簡單

public Double computePrice(String type, Double price) {
    if ("VIP".equals(type)) {
        // 普通會員9折優惠
        return price * 0.9;
    } else if ("S_VIP".equals(type)) {
        // 超級會員8折優惠
        return price * 0.8;
    } else {
        // 非會員無優惠
        return price;
    }
}

幾天後。。。

產品經理:我們增加了活動送的一個月體驗會員,與普通會員享受同等九折待遇,但是只能享受最高20的優惠金額。

靚仔:明白,不就是「窮逼vip」嗎

於是就在原來的代碼上改了改

public Double computePrice(String type, Double price) {
    if ("VIP".equals(type)) {
        // 普通會員9折優惠
        return price * 0.9;
    } else if ("S_VIP".equals(type)) {
        // 超級會員8折優惠
        return price * 0.8;
    } else if ("BEGGAR_VIP".equals(type)) {
        // 窮逼vip 9折優惠,最大優惠金額20
        return price > 200 ? (price - 20) : price * 0.9;
    }else {
        // 非會員無優惠
        return price;
    }
}

有沒有覺得 if-else 特別多,而且一旦再增加會員種類,那麼看上去就更繁瑣,代碼耦合嚴重,維護起來十分不方便。

怎麼辦,重構一下代碼唄

首先提取出價格計算的接口類

public interface PriceStrategy {
    Double computePrice(Double price);
}

然後針對不同的會員類型,實現不同會員價格計算接口,提供算法

// 普通會員
public class VipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price * 0.9;
    }
}

// 超級會員
public class SVipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price * 0.8;
    }
}

// 窮逼會員
public class BeggarVipPriceStrategy implements PriceStrategy{
    @Override
    public Double computePrice(Double price) {
        return price > 200 ? (price - 20) : price * 0.9;
    }
}

增加一個上下文角色,封裝算法對高層屏蔽,高層模塊只用訪問Context

public class PriceContext {
    private PriceStrategy priceStrategy;   
    
    public PriceContext(PriceStrategy priceStrategy) {
        this.priceStrategy = priceStrategy;
    }

    public Double computePrice(Double price) {
        return priceStrategy.computePrice(price);
    }
}

外部調用,計算價格

public Double getPrice(String type, Double price) {
    PriceStrategy priceStrategy;
    if ("VIP".equals(type)) {
        priceStrategy = new VipPriceStrategy();
    } else if ("S_VIP".equals(type)) {
        priceStrategy = new SVipPriceStrategy;
    } else if ("BEGGAR_VIP".equals(type)) {
        priceStrategy = new BeggarVipPriceStrategy;
    } else {
        return price;
    }
    PriceContext priceContext = new PriceContext(priceStrategy);
    return priceContext.computePrice(price);
}

沒錯,這就是策略模式

  • 環境(Context):持有一個 Strategy 的引用。
  • 抽象策略(Strategy):這是一個抽象角色,通常由一個接口或抽象類實現。此角色給出所有的具體策略類所需的接口。
  • 具體策略(ConcreteStrategy):包裝了相關的算法或行為。

有朋友可能會問了,這和工廠模式有什麼區別嗎?

我們再來看下工廠模式。

簡單工廠模式:

看上去簡直一摸一樣吧。

其實工廠模式和設計模式一直給人一種錯覺,總感覺是一樣的,沒有絲毫的區別。

直到我看到一個網友的解讀:

工廠模式中只管生產實例,具體怎麼使用工廠實例由調用方決定,策略模式是將生成實例的使用策略放在策略類中配置後才提供調用方使用。工廠模式調用方可以直接調用工廠實例的方法屬性等,策略模式不能直接調用實例的方法屬性,需要在策略類中封裝策略後調用。

一個注重的是實例的生產,一個注重的是策略方法。

好了,這個時候再來看我們的代碼,好像越來越複雜了,雖然用策略模式將具體的算法都抽離出來了,但是 if-else 的問題還是沒有解決啊

思考一下,我們可不可以結合以下工廠模式,來去掉煩人的 if-else

可以把策略對象初始化到一個 map 進行管理

public interface PriceStrategy {
    Map<String, PriceStrategy> map = new ConcurrentHashMap(){{
        put("VIP", new VipPriceStrategy());
        put("S_VIP", new SVipPriceStrategy());
        put("BEGGAR_VIP", new BeggarVipPriceStrategy());
    }};

    Double computePrice(Double price);
}

public class PriceContext {
    private PriceStrategy priceStrategy;

    public PriceContext(String type) {
        this.priceStrategy = PriceStrategy.map.get(type);
    }

    public Double computePrice(Double price) {
        return priceStrategy.computePrice(price);
    }
}

外部調用

public Double computePrice(String type, Double price) {
    PriceContext priceContext = new PriceContext(type);
    return priceContext.computePrice(price);
}

舒服了,終於幹掉了 if-else

策略模式優缺點

優點:

  • 策略模式遵循開閉原則,實現代碼的解耦合,用戶可以在不修改原有系統的基礎上選擇算法或行為,也可以靈活地增加新的算法或行為。
  • 算法的使用就和算法本身分開,符合單一職責原則

缺點:

  • 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類
  • 策略模式可能會造成系統產生很多具體策略類

總結

其實我們在工作中使用設計模式的時候,不需要被條條框框所束縛,設計模式可以有很多變種,也可以結合幾種設計模式一起使用,別忘了使用設計模式的初衷是什麼,不要為了使用設計模式而使用設計模式。

END

往期推薦

基於 Mysql 實現一個簡易版搜索引擎

如何保證接口的冪等性?

你必須了解的分佈式事務解決方案

就這?分佈式 ID 發號器實戰

略懂設計模式之工廠模式