策略模式與模板方法模式的介紹與對比

最近在給公司的商城作第三方支付的對接,看了一下之前的微信支付,感受結合了一下以前看的設計模式,想試試能不能在上面用上。一番研究後,感受也是能夠,就是可能有點牛刀小試。php

模式定義

  1. 策略模式的定義
    策略模式定義了算法族,分別封裝起來,讓它們之間能夠互相替換,此模式讓算法的變化獨立於使用算法的客戶。
  2. 模板方法模式定義
    模板方法模式在一個方法中定義一個算法骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠在不改變算法結構的狀況下,從新定義算法中的某些步驟。

ROUND 1:多支付方式

一、 場景描述算法

微信支付有預下單、退款、訂單查詢等等接口,看了下第三方支付,一樣也是相似的三板斧。很容易,咱們會想到如下這個類圖:

clipboard.png


圖中能夠看到WxPay和SandPay都繼承了Pay,他們有一樣的行爲:下單、退款、訂單查詢,可是,咱們能夠知道他們的實現確定是不同的,這樣咱們都必須覆蓋父類的方法,來處理不同支付的細節,這樣的時候,代碼就會顯得冗餘,每一個子類都須要覆蓋父類的實現,如何讓代碼更加統一呢?

二、 問題分析編程

這個時候,咱們須要的是將行爲獨立出來,讓其封裝在特定的行爲類裏,這樣,咱們就能「指定」行爲到支付的實例。好比說,咱們想要產生一個新的第三方支付實例,咱們能夠動態的讓其實現微信支付的下單操做(固然這是不合理)。

這時,咱們用到了一個很重要的設計原則:設計模式

針對接口編程,而不是針對實現編程

咱們用接口表明每一個行爲,好比說,OrderBehavior和RefundBehavior,行爲的每一個實現都講實現其中一個接口。支付類不會實現下單和退款的接口,而是有其餘類專門實現。咱們稱這種類叫「行爲」類。和之前作法不同的地方在於,之前的作法是:行爲由超類或者子類繼承某個接口,自行實現。這兩種作法都依賴與「實現」,這樣咱們很容易被實現綁死,很難在後來改變行爲(除非寫更多的代碼)。新的設計裏,支付類將使用由接口所表示的行爲,因此實際的「實現」並不會綁死在支付類中,這樣,支付類就不用在瞭解行爲實現的細節。微信

來看一下新的類圖:

clipboard.png

以及,看看Pay抽象類中統一後的方法代碼:微信支付

<?php

class Pay{

    public $orderBehavior;
    public $refundBehavior;
    
    public function orderPay()
    {
        return $this->orderBehavior->orderPay();
    }
    
    public function refund()
    {
        return $this->refundBehavior->refund();
    }

}

而後在實現的時候,子類就能這樣動態實現this

<?php

//實例化微信支付
$pay = new WxPay();
//賦值微信下單實例
$pay->orderBehavior = new WxOrder();
//實現了微信支付的下單
$pay->orderPay();

//第三方支付同理
$pay = new SandPay();
$pay->orderBehavior = new SandOrder();
$pay->orderPay();

可能這時候有人會問,我明明能夠直接就在具體類裏實現這個方法,也不用多寫這麼多類與接口,正常來講,支付也不會改變其實現方式。
是的,通常來講,支付是不會修改的,可是若是忽然說如今不用第三方支付,所有都用微信支付,那麼咱們代碼的修改可能會不少了,可是用這種實現咱們只須要改動一個很小地方:spa

<?php

//在外部看起來咱們調用的是第三方支付
$pay = new SandPay();
//真實咱們的實現是微信支付
$pay->orderBehavior = new WxOrder();
//咱們因爲不用管實際的細節,也不用作太多的代碼改動,並且由微信支付原本的穩定實現,咱們也能很放心的說,代碼不會出現bug
$pay->orderPay();

這一種實現方式,也符合了一個設計原則:設計

多用組合,少用繼承

這裏支付對下單甚至退款的操做,實際的實現都不是經過繼承獲得的,而是經過將其餘類的結合,不只能夠將算法族封裝成類,更能夠「在運行時動態改變行爲」,只要行爲對象符合正確的接口標準便可。code

這個狀況,你以爲是用了哪一種設計模式?


ROUND 2:一樣的實現結構

一、 場景描述

在處理完多種支付的場景後,咱們在開始加入了第三方支付的代碼,在寫的過程發現,下單操做和微信支付相似,咱們須要在請求
以前,組裝請求必要參數、簽名,獲得返回結果後,對數據驗籤,而後進行本身的業務邏輯,最後返回一個應答。
  同理,退款操做也是同樣:組裝請求必要參數、簽名等等,和下單操做幾乎處理邏輯的順序或者說結構很類似,若是咱們繼續這麼
編寫,會發現不少重複代碼,這個時候要怎麼處理呢?

二、 問題分析

在解決問題以前,先說一下一個重要的設計原則:
找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼混在一塊兒。
通俗來講,就是要善於發現現實中的變與不變,抽離不變的地方,使其能複用,而後讓變化的部分自行解決處理。
  在這個場景中,很特殊的,咱們發現不論是微信的下單、退款,仍是第三方的下單、退款,他們業務的邏輯幾乎是一個流水線上出
來的,就是他們不變的地方,而變化的是什麼呢?很明顯就是具體的實現業務不一樣,這部分應該由業務本身實現。
  這樣,咱們將該描述用類圖表示以下:

clipboard.png

而代碼具體以下:

<?php

abstract class PayAction{

    public function doAction()
    {
        $this->generateRequestData();
        $this->generateSign();
        $this->request();
        $this->verifiedSign();
        $this->handleResponse();
        $this->returnMsg();
    }

    abstract protected function generateRequestData();
    
    abstract protected function generateSign();
    
    abstract protected function request();
    
    abstract protected function handleResponse();
    
    abstract protected function verifiedSign();
    
    abstract protected function returnMsg();
}

class WxOrder extends PayAction{

    public function orderPay()
    {
        $this->doAction();
    }
}

只針對這一系列操做,咱們能夠寫成這個樣子,這樣全部的行爲都同樣了,只須要實現自身不同的地方便可。

ROUND 3:結合的效果

一、若是把第一種狀況的結構和第二種狀況的結構,結合起來,最後是什麼樣子呢,咱們能夠看一下:

clipboard.png

這樣,整個結構就很清晰了,客戶端調用實現了行爲的下單或者退款類,不須要知道具體的細節,而且能夠動態的改變行爲的對象;而行爲的操做的重複部分又被抽出來,只須要各自實現變化的部分,不變的部分都由抽象類PayAction先定義好,再由不同的支付類實現公共的部分:簽名與驗籤,甚至最後固定的返回正確和錯誤時的格式都是同一個支付裏,相同的部分;最後不同的只有不一樣請求時候,不同的請求參數,以及得到返回參數後的不同的處理了。
類和接口的確比一開始的設計要多了很多,可是結構很是清晰,並且修改的時候,能夠頗有針對性的修改,對於客戶端來講,調用是透明的,對於提供服務的咱們來講,不論是新增一個支付方式,仍是多了一個支付操做,咱們均可以很好的增長代碼,不用修改現有代碼,而這個也是一個常常說到的設計原則:

開閉原則:對修改關閉,對擴展開放

ROUND 4:解答與對比

第一和第二個場景,它們分別對應的設計模式是什麼?

第一種設計模式是:策略模式,而第二種是:模板方法模式

第一種咱們能夠清楚的看到,咱們對於如何運行一個行爲的時候,咱們是將其封裝在一個類中處理,而且,它們能夠相互替換。算法的改變由客戶來決定,能夠動態的改變。第二種咱們能夠看到的是,整個算法的結構已經被定義好,跟着預約好的模板來編寫咱們的算法,就能夠實現類似的功能有條不紊的編寫下去,不會出現多餘的部分,而且能夠專心的處理自身特別的業務邏輯。因此,之後若是代碼的相關結構很類似,能夠選用模板方法模式來編寫;若是業務中的某些行爲能夠被抽象而且有須要動態改變的時候,能夠考慮策略模式來編寫。

相關文章
相關標籤/搜索