天天看點

研磨設計模式之政策模式-6

3.4  政策模式結合模闆方法模式

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

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

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

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

        在上一章我們讨論過模闆方法模式來結合政策模式的方式,也就是主要的結構是模闆方法模式,局部采用政策模式。而這裡讨論的是政策模式來結合模闆方法模式,也就是主要的結構是政策模式,局部實作上采用模闆方法模式。通過這個示例也可以看出來,模式之間的結合是沒有定勢的,要具體問題具體分析。

        此時政策模式結合模闆方法模式的系統結構如下圖5所示:

研磨設計模式之政策模式-6

                                    圖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

繼續閱讀