設計模式-策略模式(轉)

滄海一滴發表於2014-04-07

http://my.oschina.net/bayuanqian/blog/133439

 

看到這裡,可能有朋友會想,那麼到底應該如何實現,才能夠讓價格類中的計算報價的演算法,能很容易的實現可維護、可擴充套件,又能動態的切換變化呢?

2  解決方案

2.1  策略模式來解決

用來解決上述問題的一個合理的解決方案就是策略模式。那麼什麼是策略模式呢?

(1)策略模式定義

定義一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得演算法可獨立於使用它的客戶而變化。

(2)應用策略模式來解決的思路

仔細分析上面的問題,先來把它抽象一下,各種計算報價的計算方式就好比是具體的演算法,而使用這些計算方式來計算報價的程式,就相當於是使用演算法的客戶。

再分析上面的實現方式,為什麼會造成那些問題,根本原因,就在於演算法和使用演算法的客戶是耦合的,甚至是密不可分的,在上面實現中,具體的演算法和使用演算法的客戶是同一個類裡面的不同方法。

現在要解決那些問題,按照策略模式的方式,應該先把所有的計算方式獨立出來,每個計算方式做成一個單獨的演算法類,從而形成一系列的演算法,並且為這一系列算 法定義一個公共的介面,這些演算法實現是同一介面的不同實現,地位是平等的,可以相互替換。這樣一來,要擴充套件新的演算法就變成了增加一個新的演算法實現類,要維 護某個演算法,也只是修改某個具體的演算法實現即可,不會對其它程式碼造成影響。也就是說這樣就解決了可維護、可擴充套件的問題。

為了實現讓演算法能獨立於使用它的客戶,策略模式引入了一個上下文的物件,這個物件負責持有演算法,但是不負責決定具體選用哪個演算法,把選擇演算法的功能交給了 客戶,由客戶選擇好具體的演算法後,設定到上下文物件裡面,讓上下文物件持有客戶選擇的演算法,當客戶通知上下文物件執行功能的時候,上下文物件會去轉調具體 的演算法。這樣一來,具體的演算法和直接使用演算法的客戶是分離的。

具體的演算法和使用它的客戶分離過後,使得演算法可獨立於使用它的客戶而變化,並且能夠動態的切換需要使用的演算法,只要客戶端動態的選擇使用不同的演算法,然後設定到上下文物件中去,實際呼叫的時候,就可以呼叫到不同的演算法。

 

3  模式講解

3.1  認識策略模式

(1)策略模式的功能

策略模式的功能是把具體的演算法實現,從具體的業務處理裡面獨立出來,把它們實現成為單獨的演算法類,從而形成一系列的演算法,並讓這些演算法可以相互替換。

策略模式的重心不是如何來實現演算法,而是如何組織、呼叫這些演算法,從而讓程式結構更靈活、具有更好的維護性和擴充套件性。

(2)策略模式和if-else語句

看了前面的示例,很多朋友會發現,每個策略演算法具體實現的功能,就是原來在if-else結構中的具體實現。

沒錯,其實多個if-elseif語句表達的就是一個平等的功能結構,你要麼執行if,要不你就執行else,或者是elseif,這個時候,if塊裡面的實現和else塊裡面的實現從執行地位上來講就是平等的。

而策略模式就是把各個平等的具體實現封裝到單獨的策略實現類了,然後通過上下文來與具體的策略類進行互動。

因此多個if-else語句可以考慮使用策略模式。

(3)演算法的平等性

策略模式一個很大的特點就是各個策略演算法的平等性。對於一系列具體的策略演算法,大家的地位是完全一樣的,正是因為這個平等性,才能實現演算法之間可以相互替換。

所有的策略演算法在實現上也是相互獨立的,相互之間是沒有依賴的。

所以可以這樣描述這一系列策略演算法:策略演算法是相同行為的不同實現

(4)誰來選擇具體的策略演算法

在策略模式中,可以在兩個地方來進行具體策略的選擇。

一個是在客戶端,在使用上下文的時候,由客戶端來選擇具體的策略演算法,然後把這個策略演算法設定給上下文。前面的示例就是這種情況。

還有一個是客戶端不管,由上下文來選擇具體的策略演算法,這個在後面講容錯恢復的時候給大家演示一下。

(5)Strategy的實現方式

在前面的示例中,Strategy都是使用的介面來定義的,這也是常見的實現方式。但是如果多個演算法具有公共功能的話,可以把Strategy實現成為抽象類,然後把多個演算法的公共功能實現到Strategy裡面。

(6)執行時策略的唯一性

執行期間,策略模式在每一個時刻只能使用一個具體的策略實現物件,雖然可以動態的在不同的策略實現中切換,但是同時只能使用一個。

(7)增加新的策略

在前面的示例裡面,體會到了策略模式中切換演算法的方便,但是增加一個新的演算法會怎樣呢?比如現在要實現如下的功能:對於公司的“戰略合作客戶”,統一8折。

其實很簡單,策略模式可以讓你很靈活的擴充套件新的演算法。具體的做法是:先寫一個策略演算法類來實現新的要求,然後在客戶端使用的時候指定使用新的策略演算法類就可以了。

 

除了客戶端發生變化外,已有的上下文、策略介面定義和策略的已有實現,都不需要做任何的修改,可見能很方便的擴充套件新的策略演算法。

(8)策略模式呼叫順序示意圖

策略模式的呼叫順序,有兩種常見的情況,一種如同前面的示例,具體如下:

a:先是客戶端來選擇並建立具體的策略物件

b:然後客戶端建立上下文

c:接下來客戶端就可以呼叫上下文的方法來執行功能了,在呼叫的時候,從客戶端傳入演算法需要的引數

d:上下文接到客戶的呼叫請求,會把這個請求轉發給它持有的Strategy

 

3.3  Context和Strategy的關係

在策略模式中,通常是上下文使用具體的策略實現物件,反過來,策略實現物件也可以從上下文獲取所需要的資料,因此可以將上下文當引數傳遞給策略實現物件,這種情況下上下文和策略實現物件是緊密耦合的。
在這種情況下,上下文封裝著具體策略物件進行演算法運算所需要的資料,具體策略物件通過回撥上下文的方法來獲取這些資料。

甚至在某些情況下,策略實現物件還可以回撥上下文的方法來實現一定的功能,這種使用場景下,上下文變相充當了多個策略演算法實現的公共介面,在上下文定義的方法可以當做是所有或者是部分策略演算法使用的公共功能。

但是請注意,由於所有的策略實現物件都實現同一個策略介面,傳入同一個上下文,可能會造成傳入的上下文資料的浪費,因為有的演算法會使用這些資料,而有的演算法不會使用,但是上下文和策略物件之間互動的開銷是存在的了。

還是通過例子來說明。

1:工資支付的實現思路

考慮這樣一個功能:工資支付方式的問題,很多企業的工資支付方式是很靈活的,可支付方式是比較多 的,比如:人民幣現金支付、美元現金支付、銀行轉賬到工資帳戶、銀行轉賬到工資卡;一些創業型的企業為了留住骨幹員工,還可能有:工資轉股權等等方式。總 之一句話,工資支付方式很多。

隨著公司的發展,會不斷有新的工資支付方式出現,這就要求能方便的擴充套件;另外工資支付方式不是固定的,是由公司和員工協商確定的,也就是說可能不同的員工 採用的是不同的支付方式,甚至同一個員工,不同時間採用的支付方式也可能會不同,這就要求能很方便的切換具體的支付方式。

要實現這樣的功能,策略模式是一個很好的選擇。在實現這個功能的時候,不同的策略演算法需要的資料是不一樣,比如:現金支付就不需要銀行帳號,而銀行轉賬就 需要帳號。這就導致在設計策略介面中的方法時,不太好確定引數的個數,而且,就算現在把所有的引數都列上了,今後擴充套件呢?難道再來修改策略介面嗎?如果這 樣做,那無異於一場災難,加入一個新策略,就需要修改介面,然後修改所有已有的實現,不瘋掉才怪!那麼到底如何實現,在今後擴充套件的時候才最方便呢?

解決方案之一,就是把上下文當做引數傳遞給策略物件,這樣一來,如果要擴充套件新的策略實現,只需要擴充套件上下文就可以了,已有的實現不需要做任何的修改。

這樣是不是能很好的實現功能,並具有很好的擴充套件性呢?還是通過程式碼示例來具體的看。假設先實現人民幣現金支付和美元現金支付這兩種支付方式,然後就進行使用測試,然後再來新增銀行轉賬到工資卡的支付方式,看看是不是能很容易的與已有的實現結合上。

 

3.4  策略模式結合模板方法模式

在實際應用策略模式的過程中,經常會出現這樣一種情況,就是發現這一系列演算法的實現上存在公共功能,甚至這一系列演算法的實現步驟都是一樣的,只是在某些區域性步驟上有所不同,這個時候,就需要對策略模式進行些許的變化使用了。

對於一系列演算法的實現上存在公共功能的情況,策略模式可以有如下三種實現方式:

  • 一個是在上下文當中實現公共功能,讓所有具體的策略演算法回撥這些方法。
  • 另外一種情況就是把策略的介面改成抽象類,然後在裡面實現具體演算法的公共功能。
  • 還有一種情況是給所有的策略演算法定義一個抽象的父類,讓這個父類去實現策略的介面,然後在這個父類裡面去實現公共的功能。

更進一步,如果這個時候發現“一系列演算法的實現步驟都是一樣的,只是在某些區域性步驟上有所不同” 的情況,那就可以在這個抽象類裡面定義演算法實現的骨架,然後讓具體的策略演算法去實現變化的部分。這樣的一個結構自然就變成了策略模式來結合模板方法模式 了,那個抽象類就成了模板方法模式的模板類。

在上一章我們討論過模板方法模式來結合策略模式的方式,也就是主要的結構是模板方法模式,區域性採用策略模式。而這裡討論的是策略模式來結合模板方法模式, 也就是主要的結構是策略模式,區域性實現上採用模板方法模式。通過這個示例也可以看出來,模式之間的結合是沒有定勢的,要具體問題具體分析。

 

3.5  策略模式的優缺點
    • 定義一系列演算法
      策略模式的功能就是定義一系列演算法,實現讓這些演算法可以相互替換。所以會為這一系列演算法定義公共的介面,以約束一系列演算法要實現的功能。如果這一系列演算法 具有公共功能,可以把策略介面實現成為抽象類,把這些公共功能實現到父類裡面,對於這個問題,前面講了三種處理方法,這裡就不羅嗦了。
    • 避免多重條件語句
      根據前面的示例會發現,策略模式的一系列策略演算法是平等的,可以互換的,寫在一起就是通過if-else結構來組織,如果此時具體的演算法實現裡面又有條件 語句,就構成了多重條件語句,使用策略模式能避免這樣的多重條件語句。
      如下示例來演示了不使用策略模式的多重條件語句,示例程式碼如下: 

 

    • 更好的擴充套件性
      在策略模式中擴充套件新的策略實現非常容易,只要增加新的策略實現類,然後在選擇使用策略的地方選擇使用這個新的策略實現就好了。
    • 客戶必須瞭解每種策略的不同
      策略模式也有缺點,比如讓客戶端來選擇具體使用哪一個策略,這就可能會讓客戶需要了解所有的策略,還要了解各種策略的功能和不同,這樣才能做出正確的選擇,而且這樣也暴露了策略的具體實現。
    • 增加了物件數目
      由於策略模式把每個具體的策略實現都單獨封裝成為類,如果備選的策略很多的話,那麼物件的數目就會很可觀。
    • 只適合扁平的演算法結構
      策略模式的一系列演算法地位是平等的,是可以相互替換的,事實上構成了一個扁平的演算法結構,也就是在一個策略介面下,有多個平等的策略演算法,就相當於兄弟算 法。而且在執行時刻只有一個演算法被使用,這就限制了演算法使用的層級,使用的時候不能巢狀使用。
      對於出現需要巢狀使用多個演算法的情況,比如折上折、折後返卷等業務的實現,需要組合或者是巢狀使用多個演算法的情況,可以考慮使用裝飾模式、或是變形的職責鏈、或是AOP等方式來實現。
3.6  思考策略模式

1:策略模式的本質

策略模式的本質:分離演算法,選擇實現

仔細思考策略模式的結構和實現的功能,會發現,如果沒有上下文,策略模式就回到了最基本的介面和實現了,只要是面向介面程式設計的,那麼就能夠享受到介面的封 裝隔離帶來的好處。也就是通過一個統一的策略介面來封裝和隔離具體的策略演算法,面向介面程式設計的話,自然不需要關心具體的策略實現,也可以通過使用不同的實 現類來例項化介面,從而實現切換具體的策略。

看起來好像沒有上下文什麼事情,但是如果沒有上下文,那麼就需要客戶端來直接與具體的策略互動,尤其是當需要提供一些公共功能,或者是相關狀態儲存的時 候,會大大增加客戶端使用的難度。因此,引入上下文還是很必要的,有了上下文,這些工作就由上下文來完成了,客戶端只需要與上下文互動就可以了,這樣會讓 整個設計模式更獨立、更有整體性,也讓客戶端更簡單。

但縱觀整個策略模式實現的功能和設計,它的本質還是“分離演算法,選擇實現”,因為分離並封裝了演算法,才能夠很容易的修改和新增演算法;也能很容易的動態切換使用不同的演算法,也就是動態選擇一個演算法來實現需要的功能了。

2:對設計原則的體現

從設計原則上來看,策略模式很好的體現了開-閉原則。策略模式通過把一系列可變的演算法進行封裝, 並定義出合理的使用結構,使得在系統出現新演算法的時候,能很容易的把新的演算法加入到已有的系統中,而已有的實現不需要做任何修改。這在前面的示例中已經體 現出來了,好好體會一下。

從設計原則上來看,策略模式還很好的體現了里氏替換原則。策略模式是一個扁平結構,一系列的實現演算法其實是兄弟關係,都是實現同一個介面或者繼承的同一個 父類。這樣只要使用策略的客戶保持面向抽象型別程式設計,就能夠使用不同的策略的具體實現物件來配置它,從而實現一系列演算法可以相互替換。

3:何時選用策略模式

建議在如下情況中,選用策略模式:

  • 出現有許多相關的類,僅僅是行為有差別的情況,可以使用策略模式來使用多個行為中的一個來配置一個類的方法,實現演算法動態切換
  • 出現同一個演算法,有很多不同的實現的情況,可以使用策略模式來把這些“不同的實現”實現成為一個演算法的類層次
  • 需要封裝演算法中,與演算法相關的資料的情況,可以使用策略模式來避免暴露這些跟演算法相關的資料結構
  • 出現抽象一個定義了很多行為的類,並且是通過多個if-else語句來選擇這些行為的情況,可以使用策略模式來代替這些條件語句
3.7  相關模式
    • 策略模式和狀態模式
      這兩個模式從模式結構上看是一樣的,但是實現的功能是不一樣的。
      狀態模式是根據狀態的變化來選擇相應的行為,不同的狀態對應不同的類,每個狀態對應的類實現了該狀態對應的功能,在實現功能的同時,還會維護狀態資料的變化。這些實現狀態對應的功能的類之間是不能相互替換的。
      策略模式是根據需要或者是客戶端的要求來選擇相應的實現類,各個實現類是平等的,是可以相互替換的。
      另外策略模式可以讓客戶端來選擇需要使用的策略演算法,而狀態模式一般是由上下文,或者是在狀態實現類裡面來維護具體的狀態資料,通常不由客戶端來指定狀態。
    • 策略模式和模板方法模式
      這兩個模式可組合使用,如同前面示例的那樣。
      模板方法重在封裝演算法骨架,而策略模式重在分離並封裝演算法實現。
    • 策略模式和享元模式
      這兩個模式可組合使用。
      策略模式分離並封裝出一系列的策略演算法物件,這些物件的功能通常都比較單一,很多時候就是為了實現某個演算法的功能而存在,因此,針對這一系列的、多個細粒 度的物件,可以應用享元模式來節省資源,但前提是這些演算法物件要被頻繁的使用,如果偶爾用一次,就沒有必要做成享元了。

 

 

 

 

相關文章