算法的封装与切换——策略模式
本文轉(zhuǎn)載自 :http://blog.csdn.net/lovelion/article/details/7818983
俗話說:條條大路通羅馬。在很多情況下,實(shí)現(xiàn)某個(gè)目標(biāo)的途徑不止一條,例如我們?cè)谕獬雎糜螘r(shí)可以選擇多種不同的出行方式,如騎自行車、坐汽車、坐火車或者坐飛機(jī),可根據(jù)實(shí)際情況(目的地、旅游預(yù)算、旅游時(shí)間等)來選擇一種最適合的出行方式。在制訂旅行計(jì)劃時(shí),如果目的地較遠(yuǎn)、時(shí)間不多,但不差錢,可以選擇坐飛機(jī)去旅游;如果目的地雖遠(yuǎn)、但假期長(zhǎng)、且需控制旅游成本時(shí)可以選擇坐火車或汽車;如果從健康和環(huán)保的角度考慮,而且有足夠的毅力,自行車游或者徒步旅游也是個(gè)不錯(cuò)的選擇,。
????? 在軟件開發(fā)中,我們也常常會(huì)遇到類似的情況,實(shí)現(xiàn)某一個(gè)功能有多條途徑,每一條途徑對(duì)應(yīng)一種算法,此時(shí)我們可以使用一種設(shè)計(jì)模式來實(shí)現(xiàn)靈活地選擇解決途徑,也能夠方便地增加新的解決途徑。本章我們將介紹一種為了適應(yīng)算法靈活性而產(chǎn)生的設(shè)計(jì)模式——策略模式。
?
24.1 電影票打折方案
| ????? Sunny軟件公司為某電影院開發(fā)了一套影院售票系統(tǒng),在該系統(tǒng)中需要為不同類型的用戶提供不同的電影票打折方式,具體打折方案如下: ????? (1)?學(xué)生憑學(xué)生證可享受票價(jià)8折優(yōu)惠; ????? (2)?年齡在10周歲及以下的兒童可享受每張票減免10元的優(yōu)惠(原始票價(jià)需大于等于20元); ????? (3)?影院VIP用戶除享受票價(jià)半價(jià)優(yōu)惠外還可進(jìn)行積分,積分累計(jì)到一定額度可換取電影院贈(zèng)送的獎(jiǎng)品。 ????? 該系統(tǒng)在將來可能還要根據(jù)需要引入新的打折方式。 |
????? 為了實(shí)現(xiàn)上述電影票打折功能,Sunny軟件公司開發(fā)人員設(shè)計(jì)了一個(gè)電影票類MovieTicket,其核心代碼片段如下所示:
[java]?view plaincopy????? 編寫如下客戶端測(cè)試代碼:
[java]?view plaincopy????? 編譯并運(yùn)行程序,輸出結(jié)果如下所示:
| 原始價(jià)為:60.0 --------------------------------- 學(xué)生票: 折后價(jià)為:48.0 --------------------------------- 兒童票: 折后價(jià)為:50.0 |
????? 通過MovieTicket類實(shí)現(xiàn)了電影票的折后價(jià)計(jì)算,該方案解決了電影票打折問題,每一種打折方式都可以稱為一種打折算法,更換打折方式只需修改客戶端代碼中的參數(shù),無須修改已有源代碼,但該方案并不是一個(gè)完美的解決方案,它至少存在如下三個(gè)問題:
????? (1)?MovieTicket類的calculate()方法非常龐大,它包含各種打折算法的實(shí)現(xiàn)代碼,在代碼中出現(xiàn)了較長(zhǎng)的if…else…語句,不利于測(cè)試和維護(hù)。
????? (2)?增加新的打折算法或者對(duì)原有打折算法進(jìn)行修改時(shí)必須修改MovieTicket類的源代碼,違反了“開閉原則”,系統(tǒng)的靈活性和可擴(kuò)展性較差。
????? (3)?算法的復(fù)用性差,如果在另一個(gè)系統(tǒng)(如商場(chǎng)銷售管理系統(tǒng))中需要重用某些打折算法,只能通過對(duì)源代碼進(jìn)行復(fù)制粘貼來重用,無法單獨(dú)重用其中的某個(gè)或某些算法(重用較為麻煩)。
????? 如何解決這三個(gè)問題?導(dǎo)致產(chǎn)生這些問題的主要原因在于MovieTicket類職責(zé)過重,它將各種打折算法都定義在一個(gè)類中,這既不便于算法的重用,也不便于算法的擴(kuò)展。因此我們需要對(duì)MovieTicket類進(jìn)行重構(gòu),將原本龐大的MovieTicket類的職責(zé)進(jìn)行分解,將算法的定義和使用分離,這就是策略模式所要解決的問題,下面將進(jìn)入策略模式的學(xué)習(xí)。
24.2 策略模式概述
????? 在策略模式中,我們可以定義一些獨(dú)立的類來封裝不同的算法,每一個(gè)類封裝一種具體的算法,在這里,每一個(gè)封裝算法的類我們都可以稱之為一種策略(Strategy),為了保證這些策略在使用時(shí)具有一致性,一般會(huì)提供一個(gè)抽象的策略類來做規(guī)則的定義,而每種算法則對(duì)應(yīng)于一個(gè)具體策略類。
??????策略模式的主要目的是將算法的定義與使用分開,也就是將算法的行為和環(huán)境分開,將算法的定義放在專門的策略類中,每一個(gè)策略類封裝了一種實(shí)現(xiàn)算法,使用算法的環(huán)境類針對(duì)抽象策略類進(jìn)行編程,符合“依賴倒轉(zhuǎn)原則”。在出現(xiàn)新的算法時(shí),只需要增加一個(gè)新的實(shí)現(xiàn)了抽象策略類的具體策略類即可。策略模式定義如下:
| 策略模式(Strategy Pattern):定義一系列算法類,將每一個(gè)算法封裝起來,并讓它們可以相互替換,策略模式讓算法獨(dú)立于使用它的客戶而變化,也稱為政策模式(Policy)。策略模式是一種對(duì)象行為型模式。 |
????? 策略模式結(jié)構(gòu)并不復(fù)雜,但我們需要理解其中環(huán)境類Context的作用,其結(jié)構(gòu)如圖24-1所示:
????? 在策略模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
????? ●?Context(環(huán)境類):環(huán)境類是使用算法的角色,它在解決某個(gè)問題(即實(shí)現(xiàn)某個(gè)方法)時(shí)可以采用多種策略。在環(huán)境類中維持一個(gè)對(duì)抽象策略類的引用實(shí)例,用于定義所采用的策略。
????? ●?Strategy(抽象策略類):它為所支持的算法聲明了抽象方法,是所有策略類的父類,它可以是抽象類或具體類,也可以是接口。環(huán)境類通過抽象策略類中聲明的方法在運(yùn)行時(shí)調(diào)用具體策略類中實(shí)現(xiàn)的算法。
????? ●?ConcreteStrategy(具體策略類):它實(shí)現(xiàn)了在抽象策略類中聲明的算法,在運(yùn)行時(shí),具體策略類將覆蓋在環(huán)境類中定義的抽象策略類對(duì)象,使用一種具體的算法實(shí)現(xiàn)某個(gè)業(yè)務(wù)處理。
|
????? 策略模式是一個(gè)比較容易理解和使用的設(shè)計(jì)模式,策略模式是對(duì)算法的封裝,它把算法的責(zé)任和算法本身分割開,委派給不同的對(duì)象管理。策略模式通常把一個(gè)系列的算法封裝到一系列具體策略類里面,作為抽象策略類的子類。在策略模式中,對(duì)環(huán)境類和抽象策略類的理解非常重要,環(huán)境類是需要使用算法的類。在一個(gè)系統(tǒng)中可以存在多個(gè)環(huán)境類,它們可能需要重用一些相同的算法。
????? 在使用策略模式時(shí),我們需要將算法從Context類中提取出來,首先應(yīng)該創(chuàng)建一個(gè)抽象策略類,其典型代碼如下所示:
[java]?view plaincopy?????? 然后再將封裝每一種具體算法的類作為該抽象策略類的子類,如下代碼所示:
[java]?view plaincopy????? 其他具體策略類與之類似,對(duì)于Context類而言,在它與抽象策略類之間建立一個(gè)關(guān)聯(lián)關(guān)系,其典型代碼如下所示:
[java]?view plaincopy????? 在Context類中定義一個(gè)AbstractStrategy類型的對(duì)象strategy,通過注入的方式在客戶端傳入一個(gè)具體策略對(duì)象,客戶端代碼片段如下所示:
[java]?view plaincopy????? 在客戶端代碼中只需注入一個(gè)具體策略對(duì)象,可以將具體策略類類名存儲(chǔ)在配置文件中,通過反射來動(dòng)態(tài)創(chuàng)建具體策略對(duì)象,從而使得用戶可以靈活地更換具體策略類,增加新的具體策略類也很方便。策略模式提供了一種可插入式(Pluggable)算法的實(shí)現(xiàn)方案。
24.3 完整解決方案
????? 為了實(shí)現(xiàn)打折算法的復(fù)用,并能夠靈活地向系統(tǒng)中增加新的打折方式,Sunny軟件公司開發(fā)人員使用策略模式對(duì)電影院打折方案進(jìn)行重構(gòu),重構(gòu)后基本結(jié)構(gòu)如圖24-2所示:
????? 在圖24-2中,MovieTicket充當(dāng)環(huán)境類角色,Discount充當(dāng)抽象策略角色,StudentDiscount、?ChildrenDiscount?和VIPDiscount充當(dāng)具體策略角色。完整代碼如下所示:
[java]?view plaincopy????? 為了提高系統(tǒng)的靈活性和可擴(kuò)展性,我們將具體策略類的類名存儲(chǔ)在配置文件中,并通過工具類XMLUtil來讀取配置文件并反射生成對(duì)象,XMLUtil類的代碼如下所示:
[java]?view plaincopy????? 在配置文件config.xml中存儲(chǔ)了具體策略類的類名,代碼如下所示:
[html]?view plaincopy????? 編寫如下客戶端測(cè)試代碼:
[java]?view plaincopy????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| 原始價(jià)為:60.0 --------------------------------- 學(xué)生票: 折后價(jià)為:48.0 |
????? 如果需要更換具體策略類,無須修改源代碼,只需修改配置文件,例如將學(xué)生票改為兒童票,只需將存儲(chǔ)在配置文件中的具體策略類StudentDiscount改為ChildrenDiscount,如下代碼所示:
[html]?view plaincopy????? 重新運(yùn)行客戶端程序,輸出結(jié)果如下:
| 原始價(jià)為:60.0 --------------------------------- 兒童票: 折后價(jià)為:50.0 |
????? 如果需要增加新的打折方式,原有代碼均無須修改,只要增加一個(gè)新的折扣類作為抽象折扣類的子類,實(shí)現(xiàn)在抽象折扣類中聲明的打折方法,然后修改配置文件,將原有具體折扣類類名改為新增折扣類類名即可,完全符合“開閉原則”。
24.4 策略模式的兩個(gè)典型應(yīng)用
????? 策略模式實(shí)用性強(qiáng)、擴(kuò)展性好,在軟件開發(fā)中得以廣泛使用,是使用頻率較高的設(shè)計(jì)模式之一。下面將介紹策略模式的兩個(gè)典型應(yīng)用實(shí)例,一個(gè)來源于Java SE,一個(gè)來源于微軟公司推出的演示項(xiàng)目PetShop。
????? (1) Java SE的容器布局管理就是策略模式的一個(gè)經(jīng)典應(yīng)用實(shí)例,其基本結(jié)構(gòu)示意圖如圖24-3所示:
【每次看到這個(gè)LayoutManager2接口,我都在想當(dāng)時(shí)Sun公司開發(fā)人員是怎么想的!】
????? 在Java SE開發(fā)中,用戶需要對(duì)容器對(duì)象Container中的成員對(duì)象如按鈕、文本框等GUI控件進(jìn)行布局(Layout),在程序運(yùn)行期間由客戶端動(dòng)態(tài)決定一個(gè)Container對(duì)象如何布局,Java語言在JDK中提供了幾種不同的布局方式,封裝在不同的類中,如BorderLayout、FlowLayout、GridLayout、GridBagLayout和CardLayout等。在圖24-3中,Container類充當(dāng)環(huán)境角色Context,而LayoutManager作為所有布局類的公共父類扮演了抽象策略角色,它給出所有具體布局類所需的接口,而具體策略類是LayoutManager的子類,也就是各種具體的布局類,它們封裝了不同的布局方式。
????? 任何人都可以設(shè)計(jì)并實(shí)現(xiàn)自己的布局類,只需要將自己設(shè)計(jì)的布局類作為LayoutManager的子類就可以,比如傳奇的Borland公司(現(xiàn)在已是傳說,)曾在JBuilder中提供了一種新的布局方式——XYLayout,作為對(duì)JDK提供的Layout類的補(bǔ)充。對(duì)于客戶端而言,只需要使用Container類提供的setLayout()方法就可設(shè)置任何具體布局方式,無須關(guān)心該布局的具體實(shí)現(xiàn)。在JDK中,Container類的代碼片段如下:
[java]?view plaincopy????? 從上述代碼可以看出,Container作為環(huán)境類,針對(duì)抽象策略類LayoutManager進(jìn)行編程,用戶在使用時(shí),根據(jù)“里氏代換原則”,只需要在setLayout()方法中傳入一個(gè)具體布局對(duì)象即可,無須關(guān)心它的具體實(shí)現(xiàn)。
????? (2)?除了基于Java語言的應(yīng)用外,在使用其他面向?qū)ο蠹夹g(shù)開發(fā)的軟件中,策略模式也得到了廣泛的應(yīng)用。
????? 在微軟公司提供的演示項(xiàng)目PetShop 4.0中就使用策略模式來處理同步訂單和異步訂單的問題。在PetShop 4.0的BLL(Business Logic Layer,業(yè)務(wù)邏輯層)子項(xiàng)目中有一個(gè)OrderAsynchronous類和一個(gè)OrderSynchronous類,它們都繼承自IOrderStrategy接口,如圖24-4所示:
????? 在圖24-4中,OrderSynchronous以一種同步的方式處理訂單,而OrderAsynchronous先將訂單存放在一個(gè)隊(duì)列中,然后再對(duì)隊(duì)列里的訂單進(jìn)行處理,以一種異步方式對(duì)訂單進(jìn)行處理。BLL的Order類通過反射機(jī)制從配置文件中讀取策略配置的信息,以決定到底是使用哪種訂單處理方式。配置文件web.config中代碼片段如下所示:
[html]?view plaincopy??? ?用戶只需要修改配置文件即可更改訂單處理方式,提高了系統(tǒng)的靈活性。
?
24.5 策略模式總結(jié)
????? 策略模式用于算法的自由切換和擴(kuò)展,它是應(yīng)用較為廣泛的設(shè)計(jì)模式之一。策略模式對(duì)應(yīng)于解決某一問題的一個(gè)算法族,允許用戶從該算法族中任選一個(gè)算法來解決某一問題,同時(shí)可以方便地更換算法或者增加新的算法。只要涉及到算法的封裝、復(fù)用和切換都可以考慮使用策略模式。
????? 1.?主要優(yōu)點(diǎn)
????? 策略模式的主要優(yōu)點(diǎn)如下:
????? (1)?策略模式提供了對(duì)“開閉原則”的完美支持,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為,也可以靈活地增加新的算法或行為。
????? (2)?策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級(jí)結(jié)構(gòu)定義了一個(gè)算法或行為族,恰當(dāng)使用繼承可以把公共的代碼移到抽象策略類中,從而避免重復(fù)的代碼。
????? (3)?策略模式提供了一種可以替換繼承關(guān)系的辦法。如果不使用策略模式,那么使用算法的環(huán)境類就可能會(huì)有一些子類,每一個(gè)子類提供一種不同的算法。但是,這樣一來算法的使用就和算法本身混在一起,不符合“單一職責(zé)原則”,決定使用哪一種算法的邏輯和該算法本身混合在一起,從而不可能再獨(dú)立演化;而且使用繼承無法實(shí)現(xiàn)算法或行為在程序運(yùn)行時(shí)的動(dòng)態(tài)切換。
????? (4)?使用策略模式可以避免多重條件選擇語句。多重條件選擇語句不易維護(hù),它把采取哪一種算法或行為的邏輯與算法或行為本身的實(shí)現(xiàn)邏輯混合在一起,將它們?nèi)坑簿幋a(Hard Coding)在一個(gè)龐大的多重條件選擇語句中,比直接繼承環(huán)境類的辦法還要原始和落后。
????? (5)?策略模式提供了一種算法的復(fù)用機(jī)制,由于將算法單獨(dú)提取出來封裝在策略類中,因此不同的環(huán)境類可以方便地復(fù)用這些策略類。
????? 2.?主要缺點(diǎn)
????? 策略模式的主要缺點(diǎn)如下:
????? (1)?客戶端必須知道所有的策略類,并自行決定使用哪一個(gè)策略類。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時(shí)選擇恰當(dāng)?shù)乃惴āQ言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
????? (2)?策略模式將造成系統(tǒng)產(chǎn)生很多具體策略類,任何細(xì)小的變化都將導(dǎo)致系統(tǒng)要增加一個(gè)新的具體策略類。
????? (3)?無法同時(shí)在客戶端使用多個(gè)策略類,也就是說,在使用策略模式時(shí),客戶端每次只能使用一個(gè)策略類,不支持使用一個(gè)策略類完成部分功能后再使用另一個(gè)策略類來完成剩余功能的情況。
????? 3.?適用場(chǎng)景
????? 在以下情況下可以考慮使用策略模式:
????? (1)?一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種,那么可以將這些算法封裝到一個(gè)個(gè)的具體算法類中,而這些具體算法類都是一個(gè)抽象算法類的子類。換言之,這些具體算法類均有統(tǒng)一的接口,根據(jù)“里氏代換原則”和面向?qū)ο蟮亩鄳B(tài)性,客戶端可以選擇使用任何一個(gè)具體算法類,并只需要維持一個(gè)數(shù)據(jù)類型是抽象算法類的對(duì)象。
????? (2)?一個(gè)對(duì)象有很多的行為,如果不用恰當(dāng)?shù)哪J?#xff0c;這些行為就只好使用多重條件選擇語句來實(shí)現(xiàn)。此時(shí),使用策略模式,把這些行為轉(zhuǎn)移到相應(yīng)的具體策略類里面,就可以避免使用難以維護(hù)的多重條件選擇語句。
????? (3)?不希望客戶端知道復(fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu),在具體策略類中封裝算法與相關(guān)的數(shù)據(jù)結(jié)構(gòu),可以提高算法的保密性與安全性。
|
總結(jié)
以上是生活随笔為你收集整理的算法的封装与切换——策略模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 协调多个对象之间的交互——中介者模式
- 下一篇: 对象间的联动——观察者模式