略懂設計模式之策略模式

靚仔聊程式設計發表於2021-09-25

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

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

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

靚仔:就這?簡單

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 發號器實戰

略懂設計模式之工廠模式

相關文章