android架構之模板方法模式和策略模式

來源:互聯網
上載者:User
模板方法(Template Method)模式是GOF設計模式中最為常見幾個模式之一。現在流行的很多架構中(如Spring,struts等),我們都可以看到模板方法模式的廣泛應用。模板方法模式主要應用於架構設計中,在日常的應用設計中也被經常使用。    可是,我們在運用模板方法模式來解決我們的需求而進行設計時,往往忽略了一些非常重要的細節。保證架構邏輯的正常執行,不被子類破壞;怎麼讓子類擴充模板方法等。 1. 模板方法設計模式的意圖 通常我們會遇到這樣的一個問題:我們知道一個演算法所需的關鍵步聚,並確定了這些步聚的執行順序。但是某些步聚的具體實現是未知的,或者是某些步聚的實現與具體的環境相關。 模板方法模式把我們不知道具體實現的步聚封裝成抽象方法,提供一些按正確順序調用它們的具體方法(這些具體方法統稱為模板方法),這樣構成一個抽象基類。子類通過繼承這個抽象基類去實現各個步聚的抽象方法,而工作流程卻由父類來控制。 考慮一個簡單的訂單處理需求:一個客戶可以在個訂貨單中訂購多個貨物(也稱為訂貨單項目),貨物的銷售價是根據貨物的進貨價進行計算的。有些貨物可以打折的,有些是不可以打折的。每一個客戶都有一個信用額度,每張訂單的總價不能超出該客戶的信用額度。 根據上面的業務,我們可以知道處理一個訂單需要的步聚: 1. 遍曆訂貨單的訂貨單項目列表,累加所有貨物的總價格(根據訂貨單項目計算出銷售價) 2. 根據客戶號獲得客戶的信用額度 3. 把客戶號,訂單的總價格,及訂單項目列表寫入到資料庫 但是我們並不能確定怎麼計算出貨物的銷售價,怎樣根據客戶號獲得客戶的信用額度及把訂單資訊寫入資料庫這些方法的具體實現。 所以用一個抽象類別AbstractOrder確定訂單處理的邏輯,把不能確定的方法定義為抽象方法,由子類去完成具體的實現。 Java代碼 public abstract class AbstractOrder {      public Order placeOrder(int customerId , List orderItemList){   int total = 0;   for(int i = 0; i < orderItemList.size();i++){      OrderItem orderItem = (OrderItem)orderItemList.get(i);      Total  += getOrderItemPrice(orderItem) * orderItem.getQuantity();   }   if(total > getSpendingLimit(customerId)){     throw new BusinessException(“超出信用額度” + getSpendingLimit(customerId));   }     int orderId = saveOrder(customerId, total, orderItemList);   return new OrderImpl(orderId,total);        }            public abstract int getOrderItemPrice(OrderItem orderItem);      public abstract int getSpendingLimit(int customerId);      public abstract int saveOrder(int customerId, int total, List orderItemList);   }  public abstract class AbstractOrder {   public Order placeOrder(int customerId , List orderItemList){int total = 0;for(int i = 0; i < orderItemList.size();i++){   OrderItem orderItem = (OrderItem)orderItemList.get(i);   Total  += getOrderItemPrice(orderItem) * orderItem.getQuantity();}if(total > getSpendingLimit(customerId)){  throw new BusinessException(“超出信用額度” + getSpendingLimit(customerId));}int orderId = saveOrder(customerId, total, orderItemList);return new OrderImpl(orderId,total);   }      public abstract int getOrderItemPrice(OrderItem orderItem);   public abstract int getSpendingLimit(int customerId);   public abstract int saveOrder(int customerId, int total, List orderItemList);}AbstractOrder在placeOrder方法中確定了定單處理的邏輯,placeOrder方法也稱為模板方法。在placeOrder中調用了三個抽象方法。子類只需要去實現三個抽象方法,而無需要去關心定單處理的邏輯。 2.模板方法模式定義及結構 模板方法模式屬於行為模式的一種(GOF95)。準備一個抽象類別,定義一個操作中的演算法的骨架,將一些步聚聲明為抽象方法迫使子類去實現。不同的子類可以以不同的方式實現這些抽象方法。    模板方法模式的靜態結構圖表 在模板方法模式中有兩個參與者進行協作。 抽象模板類:定義一個或多個抽象操作,由子類去實現。這些操作稱為基本操作。 定義並實現一個具體操作,這個具體操作通過調用基本操作給確定頂級邏輯。這個具體操作稱為模板方法。 具體類:實現抽象模板類所定義的抽象操作。 如上面的訂單處理所示:AbstractOrder就是抽象模板類,placeOrder即是抽象模板方法。GetOrderItemPrice,getSpendingLimit和saveOrder三個抽象方法為基本操作。 具體子類能過需要去實現這三個抽象方法。不同的子類可能有著不同的實現方式。 Java代碼 Public class ConcreteOrder extends AbstractOrder{          public int getOrderItemPrice(OrderItem orderItem){   //計算貨物的售價   ……     }     public int getSpendingLimit(int customerId){   //讀取客戶的信用額度   …..     }   public int saveOrder(int customerId, int total, List orderItemList){     //寫入資料庫   ……   }   }  Public class ConcreteOrder extends AbstractOrder{    public int getOrderItemPrice(OrderItem orderItem){//計算貨物的售價……  }  public int getSpendingLimit(int customerId){//讀取客戶的信用額度…..  }public int saveOrder(int customerId, int total, List orderItemList){  //寫入資料庫……}}ConcreteOrder為AbstractOrder的具體子類,ConcreteOrder需要去完成具體的三個基本操作。同時他也具有了父類一樣的處理邏輯。把具體的實現延遲到了子類去實現,這就是模板方法模式所帶來的好處。 3.模板方法模式與控制反轉   “不要給我們打電話,我們會給你打電話”這是著名的好萊塢原則。在好萊塢,把簡曆遞交給演藝公司後就只有回家等待。由演藝公司對整個娛樂項的完全控制,演員只能被動式的接受公司的差使,在需要的環節中,完成自己的演出。模板方法模式充分的體現了“好萊塢”原則。由父類完全控制著子類的邏輯,這就是控制反轉。子類可以實現父類的可變部份,卻繼承父類的邏輯,不能改變商務邏輯。 4.模板方法模式與開閉原則   什麼是“開閉原則”?   開閉原則是指一個軟體實體應該對擴充開放,對修改關閉。 也就是說軟體實體必須是在不被修改的情況下被擴充。模板方法模式意圖是由抽象父類控制頂級邏輯,並把基本操作的實現延遲到子類去實現,這是通過繼承的手段來達到對象的複用,同時也遵守了開閉原則。 父類通過頂級邏輯,它通過定義並提供一個具體方法來實現,我們也稱之為模板方法。通常這個模板方法才是外部對象最關心的方法。在上面的訂單處理例子中, public Order placeOrder(int customerId , List orderItemList) 這個方法才是外部對象最關心的方法。所以它必須是public的,才能被外部對象所調用。 子類需要繼承父類去擴充父類的基本方法,但是它也可以覆寫父類的方法。如果子類去覆寫了父類的模板方法,從而改變了父類控制的頂級邏輯。這違反了“開閉原則”。我們在使用模板方法模式時,應該總是保證子類有正確的邏輯。所以模板方法應該定義為final的。 所以AbstractOrder 類的模板方法placeOrder方法應該定義為final Java代碼 public final  Order placeOrder(int customerId , List orderItemList)  public final  Order placeOrder(int customerId , List orderItemList)因為子類不能覆寫一個被定義為final的方法。從而保證了子類的邏輯永遠由父類所控制。 模板方法模式中,抽象類別的模板方法應該聲明為final的。 5.模板方法模式與對象的封裝性 物件導向的三大特點:繼承,封裝,多態 對象有內部狀態和外部的行為。封裝是為了資訊隱藏,通過封裝來維護對象內部資料的完整性。使得外部對象不能夠直接存取一個對象的內部狀態,而必須通過恰當的方法才能訪問。 在java語言中,採用給對象屬性和方法賦予指定的修改符(public ,protected,private)來達到封裝的目的,使得資料不被外部對象惡意的訪問及方法不被錯誤調用從而破壞對象的封裝性。 降低方法的存取層級,也就是最大化的降低方法的可見度是一種很重要的封裝手段。最大化降低方法的可見度除了可以達到資訊隱藏外,還能有效降低類之間的耦合度,降低一個類的複雜度。還可以減少開發人員發生的的錯誤調用。 一個類應該只公開外部需要調用的方法。而所有為公開方法服務的方法都應該聲明為protected或private。如是一個方法不是需要對外公開的方法,但是它需要被子類進行擴充的或調用。那麼把它定義為protected.否則應該為private. 顯而易見,模板方法模式中的聲明為abstract的基本操作都是需要迫使子類去實現的,它們僅僅是為模板方法placeOrder服務的。它們不應該被AbstractOrder所公開,所以它們應該protected. Java代碼 protected abstract int getOrderItemPrice(OrderItem orderItem);   protected abstract int getSpendingLimit(int customerId);      protected abstract int saveOrder(int customerId, int total, List orderItemList);  protected abstract int getOrderItemPrice(OrderItem orderItem);protected abstract int getSpendingLimit(int customerId);   protected abstract int saveOrder(int customerId, int total, List orderItemList);模板方法模式中,基本方法應該聲明為protcted abstract 6.模板方法模式與勾子方法(hookMethod) 上面討論模板方法模式運用於一個業務對象。事實上,架構頻繁使用模板方法模式,使得架構實現對關鍵邏輯的集中控制。 思考這樣的一個需求:我們需要為基本Spring的應用做一個測試案例的基類.用於對類的方法進行單元測試。我們知道Spring應用把需要用到的對象都定義在外部的xml檔案中,也稱為context。通常我們會把context分割成多個小的檔案,以便於管理。在測試時我們需要讀取context檔案,但是並不是每次都讀取所有的檔案。讀取這些檔案是很費時間的。所以我們想把它緩衝起來,只要這個檔案被讀取過一次,我們就把它們緩衝起來。所以我們通過擴充Junit的TestCase類來完成一個測試基類。我們需要實現緩衝的邏輯,其它開發人員只需要實現讀取設定檔的方法即可。它不用管是否具有緩衝。 Java代碼 public AbstractCacheContextTests extends TestCase{     private static Map contextMap = new HashMap();          protected ConfigurableApplicationContext applicationContext;          protected boolean hasCachedContext(Object contextKey) {           return contextKeyToContextMap.containsKey(contextKey);       }              protected ConfigurableApplicationContext getContext(Object key) {           String keyString = contextKeyString(key);           ConfigurableApplicationContext ctx =                   (ConfigurableApplicationContext) contextKeyToContextMap.get(keyString);           if (ctx == null) {               if (key instanceof String[]) {                   ctx = loadContextLocations((String[]) key);               }               contextKeyToContextMap.put(keyString, ctx);           }           return ctx;       }                  protected String contextKeyString(Object contextKey) {           if (contextKey instanceof String[]) {               return StringUtils.arrayToCommaDelimitedString((String[]) contextKey);           }           else {               return contextKey.toString();           }       }                  protected ConfigurableApplicationContext loadContextLocations(String[] locations) {           return new ClassPathXmlApplicationContext(locations);       }              //覆寫TestCase的setUp方法,在運行測試方法之前從緩衝中讀取context檔案,如果緩衝中不存在則初始化applicationContext,並放入緩衝.       protected final void setUp() throws Exception {          String[] contextFiles = getConfigLocations();          applicationContext = getContext(contextFiles);                 }              //讀取context檔案,由子類實現       protected abstract String[] getConfigLocations();     }  public AbstractCacheContextTests extends TestCase{  private static Map contextMap = new HashMap();    protected ConfigurableApplicationContext applicationContext;    protected boolean hasCachedContext(Object contextKey) {return contextKeyToContextMap.containsKey(contextKey);}protected ConfigurableApplicationContext getContext(Object key) {String keyString = contextKeyString(key);ConfigurableApplicationContext ctx =(ConfigurableApplicationContext) contextKeyToContextMap.get(keyString);if (ctx == null) {if (key instanceof String[]) {ctx = loadContextLocations((String[]) key);}contextKeyToContextMap.put(keyString, ctx);}return ctx;}protected String contextKeyString(Object contextKey) {if (contextKey instanceof String[]) {return StringUtils.arrayToCommaDelimitedString((String[]) contextKey);}else {return contextKey.toString();}}protected ConfigurableApplicationContext loadContextLocations(String[] locations) {return new ClassPathXmlApplicationContext(locations);}//覆寫TestCase的setUp方法,在運行測試方法之前從緩衝中讀取context檔案,如果緩衝中不存在則初始化applicationContext,並放入緩衝.protected final void setUp() throws Exception {   String[] contextFiles = getConfigLocations();   applicationContext = getContext(contextFiles);   }//讀取context檔案,由子類實現protected abstract String[] getConfigLocations();}這樣子類只需要去實現getConfigLocations方法,提供需要讀取的設定檔字元數組就可以了。至於怎麼去讀取context檔案內容,怎麼實現緩衝,則無需關心。 AbstractCacheContextTests保證在運行所有測試之前去執行讀取context檔案的動作。 注意這裡setUp方法被聲明為protected,是因為setUp方法是TestCase類的方法。在這裡setUp方法被定義為final了,是確保子類不能去覆寫這個方法,從而保證了父類控制的邏輯。 在這裡我們就會發現一個問題了,如果你使用過Junit你就會明白髮生了什麼問題了。TestCase的setUp方法,就是在這個測試類別的測試方法運行之前作一些初始化動作。如建立一些所有測試方法都要用到的公用對象等。我們在這裡把setUp方法聲明為final之後,子類再也無法去擴充它了,子類同時還需要一些額外的初始化動作就無法實現了。 可能你會說:“把setUp方法的final修飾符去掉就可以了啊”。這樣是可以的,但是去掉final修飾符後,子類是可以覆寫setUp方法,而去執行一些額外的初始化。而可怕的是,父類從此失去了必須讀取context檔案及緩衝context內容的邏輯。 為瞭解決這個問題,我們實現一個空方法onSetUp。在setUp方法中調用onSetUp方法。這樣子類就可以通過覆寫onSetUp方法來進行額外的初始化。 //覆寫TestCase的setUp方法,在運行測試方法之前從緩衝中讀取context檔案,如果緩衝中不存在則初始化applicationContext,並放入緩衝. Java代碼 protected final void setUp() throws Exception {          String[] contextFiles = getConfigLocations();          applicationContext = getContext(contextFiles);                    onSetUp();       }          protected void onSetUp(){       }       //讀取context檔案,由子類實現       protected abstract String[] getConfigLocations();     }  protected final void setUp() throws Exception {   String[] contextFiles = getConfigLocations();   applicationContext = getContext(contextFiles);          onSetUp();}protected void onSetUp(){ }//讀取context檔案,由子類實現protected abstract String[] getConfigLocations();}為什麼不把onSetUp聲明為abstract呢?這是因為子類不一定總是需要覆寫onSetUp方法。可以說onSetUp方法是為了對setUp方法的擴充。 像onSetUp這樣的空方法就稱之為勾子方法(HookMethod); 7.模板方法模式與策略模式   模板方法模式與策略模式的作用相常類似。有時可以用策略模式替代模板方法模式。模板方法模式通過繼承來實現代碼複用,策略模式使用委託,委託比繼承具有更大的靈活性。繼承經常被錯誤的使用。 策略模式把不確定的行為集中到一個介面中,並在主類委託這個介面。 思考上面的訂單處理例子,改為策略模式後。 1.把不確定的行為抽取為一個介面。 Java代碼 Public interface OrderHelper{         public int getOrderItemPrice(OrderItem orderItem);         public int getSpendingLimit(int customerId);         public int saveOrder(int customerId, int total, List orderItemList);   }  Public interface OrderHelper{      public int getOrderItemPrice(OrderItem orderItem);      public int getSpendingLimit(int customerId);      public int saveOrder(int customerId, int total, List orderItemList);}2. 而把這個具體類調用這個介面的相應方法來實現具體的邏輯。 Java代碼 public class  Order {      private OrderHelper orderHelpr;      public void setOrderHelper(OrderHelper orderHelper){       this.orderHelper = orderHelper;     }      public Order placeOrder(int customerId , List orderItemList){   int total = 0;   for(int i = 0; i < orderItemList.size();i++){      OrderItem orderItem = (OrderItem)orderItemList.get(i);      Total  += orderHelpr .getOrderItemPrice(orderItem) * orderItem.getQuantity();   }   if(total > orderHelpr .getSpendingLimit(customerId)){     throw new BusinessException(“超出信用額度” + orderHelpr .getSpendingLimit(customerId));   }     int orderId = orderHelpr .saveOrder(customerId, total, orderItemList);   return new OrderImpl(orderId,total);        }   }  public class  Order {   private OrderHelper orderHelpr;   public void setOrderHelper(OrderHelper orderHelper){    this.orderHelper = orderHelper;  }   public Order placeOrder(int customerId , List orderItemList){int total = 0;for(int i = 0; i < orderItemList.size();i++){   OrderItem orderItem = (OrderItem)orderItemList.get(i);   Total  += orderHelpr .getOrderItemPrice(orderItem) * orderItem.getQuantity();}if(total > orderHelpr .getSpendingLimit(customerId)){  throw new BusinessException(“超出信用額度” + orderHelpr .getSpendingLimit(customerId));}int orderId = orderHelpr .saveOrder(customerId, total, orderItemList);return new OrderImpl(orderId,total);   }}這樣Order類不再是一個抽象類別,而是一個具體類。Order類委託OrderHelpher介面來完成placeOrder方法所需的基本操作。 像在這種情況下使用原則模式更具有優勢,策略模式不需要繼承來實現。而是通過一個委派物件來實現。OrderHelper介面無需要去繼續任何指定的類。而相對來說,採用策略來實現會更複雜一些。 由此可見,模板方法模式主要應用於架構設計中,以確保基類控制處理流程的邏輯順序(如架構的初始化)。像上面的測試基類中。架構通常需要控制反轉。而在下面一些情況中,優級先考慮使用原則模式: 當需要變化的操作非常多時,採用策略模式把這些操作抽取到一個介面。 當那些基本操作的實現需要與其它類相關時,應該使用原則模式。通過委託介面把行為與實現完全分離出來(比如資料存取)。 比如訂單處理的saveOrder方法,是寫入資料庫的。它的實現與你採用何種持久化模式相關。 當某些基本操作的實現可能需要在運行時改變時,可以通過在運行時改變委派物件來實現,而繼承則不能。所以採用策略模式。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.