3.4 政策模式結合模闆方法模式
在實際應用政策模式的過程中,經常會出現這樣一種情況,就是發現這一系列算法的實作上存在公共功能,甚至這一系列算法的實作步驟都是一樣的,隻是在某些局部步驟上有所不同,這個時候,就需要對政策模式進行些許的變化使用了。
對于一系列算法的實作上存在公共功能的情況,政策模式可以有如下三種實作方式:
- 一個是在上下文當中實作公共功能,讓所有具體的政策算法回調這些方法。
- 另外一種情況就是把政策的接口改成抽象類,然後在裡面實作具體算法的公共功能。
- 還有一種情況是給所有的政策算法定義一個抽象的父類,讓這個父類去實作政策的接口,然後在這個父類裡面去實作公共的功能。
更進一步,如果這個時候發現“一系列算法的實作步驟都是一樣的,隻是在某些局部步驟上有所不同”的情況,那就可以在這個抽象類裡面定義算法實作的骨架,然後讓具體的政策算法去實作變化的部分。這樣的一個結構自然就變成了政策模式來結合模闆方法模式了,那個抽象類就成了模闆方法模式的模闆類。
在上一章我們讨論過模闆方法模式來結合政策模式的方式,也就是主要的結構是模闆方法模式,局部采用政策模式。而這裡讨論的是政策模式來結合模闆方法模式,也就是主要的結構是政策模式,局部實作上采用模闆方法模式。通過這個示例也可以看出來,模式之間的結合是沒有定勢的,要具體問題具體分析。
此時政策模式結合模闆方法模式的系統結構如下圖5所示:
圖5 政策模式結合模闆方法模式的結構示意圖
還是用實際的例子來說吧,比如上面那個記錄日志的例子,如果現在需要在所有的消息前面都添加上日志時間,也就是說現在記錄日志的步驟變成了:第一步為日志消息添加日志時間;第二步具體記錄日志。
那麼該怎麼實作呢?
(1)記錄日志的政策接口沒有變化,為了看起來友善,還是示例一下,示例代碼如下:
public interface LogStrategy { public void log(String msg); } |
(2)增加一個實作這個政策接口的抽象類,在裡面定義記錄日志的算法骨架,相當于模闆方法模式的模闆,示例代碼如下:
public abstract class LogStrategyTemplate implements LogStrategy{ public final void log(String msg) { //第一步:給消息添加記錄日志的時間 DateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss SSS"); msg = df.format(new java.util.Date())+" 内容是:"+ msg; //第二步:真正執行日志記錄 doLog(msg); } protected abstract void doLog(String msg); } |
(3)這個時候那兩個具體的日志算法實作也需要做些改變,不再直接實作政策接口了,而是繼承模闆,實作模闆方法了。這個時候記錄日志到資料庫的類,示例代碼如下:
public class DbLog extends LogStrategyTemplate{ //除了定義上發生了改變外,具體的實作沒變 public void doLog(String msg) { //制造錯誤 if(msg!=null && msg.trim().length()>5){ int a = 5/0; } System.out.println("現在把 '"+msg+"' 記錄到資料庫中"); } } |
同理實作記錄日志到檔案的類如下:
public class FileLog extends LogStrategyTemplate{ public void doLog(String msg) { System.out.println("現在把 '"+msg+"' 記錄到檔案中"); } } |
(4)算法實作的改變不影響使用算法的上下文,上下文跟前面一樣,示例代碼如下:
public class LogContext { public void log(String msg){ //在上下文裡面,自行實作對具體政策的選擇 //優先選用政策:記錄到資料庫 LogStrategy strategy = new DbLog(); try{ strategy.log(msg); }catch(Exception err){ //出錯了,那就記錄到檔案中 strategy = new FileLog(); strategy.log(msg); } } } |
(5)用戶端跟以前也一樣,示例代碼如下:
public class Client { public static void main(String[] args) { LogContext log = new LogContext(); log.log("記錄日志"); log.log("再次記錄日志"); } } |
運作一下用戶端再次測試看看,體會一下,看看結果是否帶上了時間。
通過這個示例,好好體會一下政策模式和模闆方法模式的組合使用,在實用開發中是很常見的方式。
3.5 政策模式的優缺點
-
定義一系列算法
政策模式的功能就是定義一系列算法,實作讓這些算法可以互相替換。是以會為這一系列算法定義公共的接口,以限制一系列算法要實作的功能。如果這一系列算法具有公共功能,可以把政策接口實作成為抽象類,把這些公共功能實作到父類裡面,對于這個問題,前面講了三種處理方法,這裡就不羅嗦了。
避免多重條件語句
根據前面的示例會發現,政策模式的一系列政策算法是平等的,可以互換的,寫在一起就是通過if-else結構來組織,如果此時具體的算法實作裡面又有條件語句,就構成了多重條件語句,使用政策模式能避免這樣的多重條件語句。
如下示例來示範了不使用政策模式的多重條件語句,示例代碼如下:
public class OneClass { public void oneMethod(int type){ //使用政策模式的時候,這些算法的處理代碼就被拿出去, //放到單獨的算法實作類去了,這裡就不再是多重條件了 if(type==1){ //算法一示範 //從某個地方擷取這個s的值 String s = ""; //然後判斷進行相應處理 if(s.indexOf("a") > 0){ //處理 }else{ //處理 } }else if(type==2){ //算法二示範 //從某個地方擷取這個a的值 int a = 3; //然後判斷進行相應處理 if(a > 10){ //處理 }else{ //處理 } } } } |
-
更好的擴充性
在政策模式中擴充新的政策實作非常容易,隻要增加新的政策實作類,然後在選擇使用政策的地方選擇使用這個新的政策實作就好了。
-
客戶必須了解每種政策的不同
政策模式也有缺點,比如讓用戶端來選擇具體使用哪一個政策,這就可能會讓客戶需要了解所有的政策,還要了解各種政策的功能和不同,這樣才能做出正确的選擇,而且這樣也暴露了政策的具體實作。
-
增加了對象數目
由于政策模式把每個具體的政策實作都單獨封裝成為類,如果備選的政策很多的話,那麼對象的數目就會很可觀。
-
隻适合扁平的算法結構
政策模式的一系列算法地位是平等的,是可以互相替換的,事實上構成了一個扁平的算法結構,也就是在一個政策接口下,有多個平等的政策算法,就相當于兄弟算法。而且在運作時刻隻有一個算法被使用,這就限制了算法使用的層級,使用的時候不能嵌套使用。
對于出現需要嵌套使用多個算法的情況,比如折上折、折後返卷等業務的實作,需要組合或者是嵌套使用多個算法的情況,可以考慮使用裝飾模式、或是變形的職責鍊、或是AOP等方式來實作。
3.6 思考政策模式
1:政策模式的本質
政策模式的本質:分離算法,選擇實作。
仔細思考政策模式的結構和實作的功能,會發現,如果沒有上下文,政策模式就回到了最基本的接口和實作了,隻要是面向接口程式設計的,那麼就能夠享受到接口的封裝隔離帶來的好處。也就是通過一個統一的政策接口來封裝和隔離具體的政策算法,面向接口程式設計的話,自然不需要關心具體的政策實作,也可以通過使用不同的實作類來執行個體化接口,進而實作切換具體的政策。
看起來好像沒有上下文什麼事情,但是如果沒有上下文,那麼就需要用戶端來直接與具體的政策互動,尤其是當需要提供一些公共功能,或者是相關狀态存儲的時候,會大大增加用戶端使用的難度。是以,引入上下文還是很必要的,有了上下文,這些工作就由上下文來完成了,用戶端隻需要與上下文互動就可以了,這樣會讓整個設計模式更獨立、更有整體性,也讓用戶端更簡單。
但縱觀整個政策模式實作的功能和設計,它的本質還是“分離算法,選擇實作”,因為分離并封裝了算法,才能夠很容易的修改和添加算法;也能很容易的動态切換使用不同的算法,也就是動态選擇一個算法來實作需要的功能了。
2:對設計原則的展現
從設計原則上來看,政策模式很好的展現了開-閉原則。政策模式通過把一系列可變的算法進行封裝,并定義出合理的使用結構,使得在系統出現新算法的時候,能很容易的把新的算法加入到已有的系統中,而已有的實作不需要做任何修改。這在前面的示例中已經展現出來了,好好體會一下。
從設計原則上來看,政策模式還很好的展現了裡氏替換原則。政策模式是一個扁平結構,一系列的實作算法其實是兄弟關系,都是實作同一個接口或者繼承的同一個父類。這樣隻要使用政策的客戶保持面向抽象類型程式設計,就能夠使用不同的政策的具體實作對象來配置它,進而實作一系列算法可以互相替換。
3:何時選用政策模式
建議在如下情況中,選用政策模式:
- 出現有許多相關的類,僅僅是行為有差别的情況,可以使用政策模式來使用多個行為中的一個來配置一個類的方法,實作算法動态切換
- 出現同一個算法,有很多不同的實作的情況,可以使用政策模式來把這些“不同的實作”實作成為一個算法的類層次
- 需要封裝算法中,與算法相關的資料的情況,可以使用政策模式來避免暴露這些跟算法相關的資料結構
- 出現抽象一個定義了很多行為的類,并且是通過多個if-else語句來選擇這些行為的情況,可以使用政策模式來代替這些條件語句
3.7 相關模式
-
政策模式和狀态模式
這兩個模式從模式結構上看是一樣的,但是實作的功能是不一樣的。
狀态模式是根據狀态的變化來選擇相應的行為,不同的狀态對應不同的類,每個狀态對應的類實作了該狀态對應的功能,在實作功能的同時,還會維護狀态資料的變化。這些實作狀态對應的功能的類之間是不能互相替換的。
政策模式是根據需要或者是用戶端的要求來選擇相應的實作類,各個實作類是平等的,是可以互相替換的。
另外政策模式可以讓用戶端來選擇需要使用的政策算法,而狀态模式一般是由上下文,或者是在狀态實作類裡面來維護具體的狀态資料,通常不由用戶端來指定狀态。
-
政策模式和模闆方法模式
這兩個模式可組合使用,如同前面示例的那樣。
模闆方法重在封裝算法骨架,而政策模式重在分離并封裝算法實作。
-
政策模式和享元模式
這兩個模式可組合使用。
政策模式分離并封裝出一系列的政策算法對象,這些對象的功能通常都比較單一,很多時候就是為了實作某個算法的功能而存在,是以,針對這一系列的、多個細粒度的對象,可以應用享元模式來節省資源,但前提是這些算法對象要被頻繁的使用,如果偶爾用一次,就沒有必要做成享元了。
政策模式結束,謝謝觀賞!鞠躬ing