天天看點

人人都會設計模式:政策模式

人人都會設計模式:政策模式

該圖檔由daschorsch在Pixabay上釋出

你好,我是看山。

本文收錄在《一個架構師的職業素養》專欄,日拱一卒,功不唐捐。

定義

政策模式,英文全稱是 Strategy Design Pattern。在 GoF 的《設計模式》一書中,它是這樣定義的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

翻譯成中文就是:定義一族算法類,将每個算法分别封裝起來,讓它們可以互相替換。政策模式可以使算法的變化獨立于使用它們的用戶端。

這裡所說的用戶端代指使用算法的代碼。

根據使用場景分類,政策模式是一種行為型模式,用于運作時控制類的行為或算法。

使用上的直覺感受是,政策模式可以減少了 if-else/switch 分支代碼。那減少分支代碼有什麼好處呢?

解耦代碼,政策模式就是解耦不同算法實作;

減少 bug 産生機率,減少分支,就是減少 bug 發生機率。

有程式設計經驗的都知道,很多 bug 都是從分支邏輯産生的。我剛開始工作時晚上 12 點開始抓蟲,一直抓到淩晨 2 點多,最後發現是有一個 if-else 分支中,在某個 if 前面少寫了一個 else。下面是示例,實際代碼比這個複雜很多:

if (a < 1) {
} else if (a < 2) {
} else if (a < 3) {
}
      

結果寫成了:

if (a < 1) {
} else if (a < 2) {
} if (a < 3) {
}
      

代碼編譯不會錯,但是在執行時,某些 case 會不符合預期。

問題

我們來看看政策模式出現的場景。

以電商系統的支付功能為例,最早的時候,我們可能為了更快上線,選擇一個較多人使用的支付方式,比如微信支付(也有可能是支付寶支付,根據售賣場景不同區分)。這個時候,我們隻需要判斷使用者是從 PC 頁面進入還是 H5 進入即可。

後來,業務發展比較好,涉及人群更多了,于是需要對接支付寶支付。支付寶支付也分為了多種的支付場景,對接接口變多了,但是也在可控範圍内。

再後來,我們需要對接銀聯支付、對接各銀行接口,等等,支付接口變得越來越臃腫。于是,每對接一種支付方式,支付相關接口就會增加一倍。此時,這坨臃腫的代碼,無論是修複簡單的 bug,還是微調傳輸參數,都會影響整個支付邏輯,進而增加了在已有正常運作代碼中引入錯誤的風險。

人人都會設計模式:政策模式

如果是多人協作開發,我們還會陷入代碼合并時應付各種沖突的情況。終于,在某一時刻,我們看着這一坨代碼,已經無從下手維護了。

解決方案

首先,我們來分析一下上面的場景,不變的是系統内部的支付業務邏輯,變化的是支付方式。

支付方式的可變性在于,可能會與多種支付方式對接,對接參數、協定、位址等都會不同。根據設計模式的整體思想,我們将變化的單獨出去,将不變的穩定下來。

這種處理方式就是政策模式建議的:找出負責用許多不同方式完成特定任務的類,然後将其中的算法抽取到一族被稱為政策的獨立類中。

調用這些政策類的是調用上下文,它持有對所有政策類的引用。上下文不執行任務,它是任務的指揮者,将工作委派給已連接配接的政策對象。關系如下:

人人都會設計模式:政策模式

很多教程到這裡就結束了,如果你能夠看到這裡,而且還用心看了,你就會發現一絲絲的不一樣。

根據迪米特法則(LOD,Law of Demeter),上下文不需要知道具體政策類的功能,隻需要通過特定的接口,用于觸發選中政策即可。也就是說,完整的政策模式,應該有具體的政策判斷是否由該政策執行,上下文隻需要知道有哪些政策就行了。這樣改動之後,上下文還能夠與工廠模式結合。如果政策是無狀态政策,還可以在上下文中引入單例模式。

适用與不适用

根據上面的定義,政策模式是圍繞可以互換的算法來建立業務的。簡單的說就是,分支邏輯隔離。

當你想使用各種不同算法變體,且能夠在運作時切換算法。政策模式可以将對象關聯到不同實作方式的不同子任務中,可以間接的修改對象;

隻有在執行時有些許不同的相似算法。可以将不同的行為抽象到獨立的類中,在原來的類中調用這些獨立的算法;

算法在業務邏輯中不是特别重要。我們可以通過政策模式将算法、資料、依賴等抽離出來,在運作時調用即可。

設計模式隻是解決問題的優雅實作,并不一定适用所有情況,比如下面這幾種,就可以不用非得實作政策模式:

如果算法極少變化,就沒有任何理由引入新的類和接口。

如果使用了 Java8 之後的版本,可以使用函數式程式設計,有時候就使用 Lambda 表達式或者匿名内部類的方式實作具體算法即可。

示例代碼

還是以支付為例,因為都是示範,一切從簡。我曾經主導過支付中台,如果想要具體實作,可以具體聊一下。

首先定義支付政策接口:

public interface PayStrategy {
    String payType();

    void callPay(BigDecimal amount);
}
      

payType()是在具體的政策實作中定義政策可執行的支付方式,也可以通過傳參數的方式傳回boolean類型用于判斷是否可執行。

然後是微信支付和支付寶支付分别實作支付政策接口:

public class WxpayPayStrategy implements PayStrategy {

    @Override
    public String payType() {
        return "WXPAY";
    }

    @Override
    public void callPay(BigDecimal amount) {
        // 微信支付接口
        // 這裡隻是示範,即使都是微信支付,也會分不同的接口
        System.out.println("調用微信支付接口");
    }
}

public class AlipayPayStrategy implements PayStrategy {

    @Override
    public String payType() {
        return "ALIPAY";
    }

    @Override
    public void callPay(BigDecimal amount) {
        // 調用支付寶支付接口
        // 這裡隻是示範,即使都是支付寶支付,也會分不同的接口
        System.out.println("調用支付寶支付接口");
    }
}
      

我們再來看看持有政策算法的上下文:

public class StrategyContext {
    private static final Map<String, PayStrategy> PAY_STRATEGY_MAP = new HashMap<>();

    static {
        final AlipayPayStrategy alipayPayStrategy = new AlipayPayStrategy();
        final WxpayPayStrategy wxpayPayStrategy = new WxpayPayStrategy();

        PAY_STRATEGY_MAP.put(alipayPayStrategy.payType(), alipayPayStrategy);
        PAY_STRATEGY_MAP.put(wxpayPayStrategy.payType(), wxpayPayStrategy);
    }

    public void pay(String payType, BigDecimal amount) {
        final PayStrategy payStrategy = PAY_STRATEGY_MAP.get(payType);
        payStrategy.callPay(amount);
    }
}
      

可以看到,上下文隻需要知道政策算法的存在,至于算法是否符合要求,由算法自己判斷。

調用就比較簡單了:

public class Main {
    public static void main(String[] args) {
        final StrategyContext strategyContext = new StrategyContext();
        strategyContext.pay("ALIPAY", BigDecimal.TEN);
        strategyContext.pay("WXPAY", BigDecimal.ONE);
    }
}
      

文末總結

政策模式可能用來減少分支邏輯,将不同的算法分離開來。如果配合工廠模式、單例模式,可以更加靈活的使用。如果是在 Spring 當中,借助自動注入,上下文甚至可以不知道具體政策實作。

最近剛看到一句話,“日拱一卒,功不唐捐”。堅持下去,每天學點新東西,給生活加點色彩。

推薦閱讀

Java 中的單例模式(完整篇)

設計模式:建造者模式