1. 程式人生 > >設計模式19:Template Method Pattern (模板方法模式)

設計模式19:Template Method Pattern (模板方法模式)

Define:Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

一、 模板方法(Template Method)模式

準備一個抽象類,將部分邏輯以具體方法以及具體構造子的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。這就是模版方法模式的用意。

很多人可能沒有想到,模版方法模式實際上是所有模式中最為常見的幾個模式之一,而且很多人可能使用過模版方法模式而沒有意識到自己已經使用了這個模式。模版方法模式是基於繼承的程式碼複用的基本技術,模版方法模式的結構和用法也是面向物件設計的核心。

模版方法模式需要開發抽象類和具體子類的設計師之間的協作。一個設計師負責給出一個演算法的輪廓和骨架,另一些設計師則負責給出這個演算法的各個邏輯步驟。代表這些具體邏輯步驟的方法稱做基本方法(primitive method);而將這些基本法方法總彙起來的方法叫做模版方法(template method),這個設計模式的名字就是從此而來。


二、 模版方法模式的結構

模版方法模式的靜態結構如下圖所示。

 

這裡涉及到兩個角色:

  • 抽象模版(AbstractClass)角色有如下的責任:

定義了一個或多個抽象操作,以便讓子類實現。這些抽象操作叫做基本操作,它們是一個頂級邏輯的組成步驟。

定義並實現了一個模版方法。這個模版方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能呼叫一些具體方法。

  • 具體模版(ConcreteClass)角色有如下的責任:

實現父類所定義的一個或多個抽象方法,它們是一個頂級邏輯的組成步驟。

每一個抽象模版角色都可以有任意多個具體模版角色與之對應,而每一個具體模版角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。


三、 模板方法模式的示意性程式碼


四、 繼承作為複用的工具

使用繼承作為複用的手段必須慎重,C#語言的設計師對使用繼承作為複用的工具有著不同層次上的認識。

不知其一

首先,初學C#的程式設計師可能不知道什麼是繼承,或者認為"繼承"是高深的工具。那時候,大部分的功能複用都是通過委派進行的。

知其一、不知其二

然後慢慢地,他們發現在C#語言裡實現繼承並不困難,並且初步認識到繼承可以使子類一下子得到基類的行為。這時他們就會躍躍欲試了,試圖使用繼承作為功能複用的主要工具,並把原來應當使用委派的地方,改為使用繼承,這時繼承就有被濫用的危險。

知其二

很多面向物件的設計專家從1986年就開始警告繼承關係被濫用的可能。有一些面向物件的程式語言,如SELF語言,甚至將類的繼承關係從語言的功能中取消掉,改為完全使用委派。

其他的設計師雖然不提倡徹底取消繼承,但無一例外地鼓勵在設計中儘可能使甩委派關係代替繼承關係。比如在【GOF95】一書中,狀態模式、策略模式、裝飾模式、橋樑模式以及抽象工廠模式均是將依賴於繼承的實現轉換為基於物件的組合和聚合的實現,這些模式的要點就是使用委派關係代替繼承關係。

知其三

是不是繼承就根本不該使用呢?事實上對資料的抽象化、繼承、封裝和多型性並稱C#和其他絕大多數的面嚮物件語言的幾項最重要的特性。繼承不應當被濫用,並不意味著繼承根本就不該使用。因為繼承容易被濫用就徹底拋棄繼承,無異於因噎廢食。

繼承使得型別的等級結構易於理解、維護和擴充套件,而型別的等級結構非常適合於抽象化的設計、實現和複用。儘管【GOF95】所給出的設計模式基本上沒有太多基於繼承的模式,很多模式都是用繼承的辦法定義、實現介面的。多數的設計模式都描寫一個以抽象類作為基類,以具體類作為實現的等級結構,比如介面卡模式、合成模式、橋樑模式、狀態模式等。

模版方法模式則更進了一步:此模式鼓勵恰當地使用繼承。此模式可以用來改寫一些擁有相同功能的相關的類,將可複用的一般性的行為程式碼移到基類裡面,而把特殊化的行為程式碼移到子類裡面。

因此,熟悉模版方法模式便成為一個重新學習繼承的好地方。


五、 一個實際應用模板方法的例子

下面的例子演示了資料庫訪問的模板方法。實際應用時,請確保C盤根目錄下有nwind.mdb這個Access資料庫(可以從Office的安裝目錄下找到。中文版使用者的請注意欄位名可能有所不同)。

  

六、 模版方法模式中的方法

模版方法中的方法可以分為兩大類:模版方法(Template Method)和基本方法(Primitive Method)。

模版方法

一個模版方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總演算法或一個總行為的方法。這個模版方法一般會在抽象類中定義,並由子類不加以修改地完全繼承下來。

基本方法

基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。

抽象方法:一個抽象方法由抽象類宣告,由具體子類實現。在C#語言裡一個抽象方法以abstract關鍵字標示出來。

具體方法:一個具體方法由抽象類宣告並實現,而子類並不實現或置換。在C#語言裡面,一個具體方法沒有abstract關鍵字。

鉤子方法:一個鉤子方法由抽象類宣告並實現,而子類會加以擴充套件。通常抽象類給出的實現是一個空實現,作為方法的預設實現。(Visual FoxPro中專案嚮導建立的專案會使用一個AppHook類實現監視專案成員變化,調整系統結構的工作。)鉤子方法的名字通常以do開始。


七、 重構的原則

在對一個繼承的等級結構做重構時,一個應當遵從的原則便是將行為儘量移動到結構的高階,而將狀態儘量移動到結構的低端。

1995年,Auer曾在文獻【AUER95】中指出:

  1. 應當根據行為而不是狀態定義一個類。也就是說,一個類的實現首先建立在行為的基礎之上,而不是建立在狀態的基礎之上。
  2. 在實現行為時,是用抽象狀態而不是用具體狀態。如果一個行為涉及到物件的狀態時,使用間接的引用而不是直接的引用。換言之,應當使用取值方法而不是直接引用屬性。
  3. 給操作劃分層次。一個類的行為應當放到一個小組核心方法(Kernel Methods)裡面,這些方法可以很方便地在子類中加以置換。
  4.  將狀態屬性的確認推遲到子類中。不要在抽象類中過早地宣告屬性變數,應將它們儘量地推遲到子類中去宣告。在抽象超類中,如果需要狀態屬性的話,可以呼叫抽象的取值方法,而將抽象的取值方法的實現放到具體子類中。

如果能夠遵從這樣的原則,那麼就可以在等級結構中將介面與實現分隔開來,將抽象與具體分割開來,從而保證程式碼可以最大限度地被複用。這個過程實際上是將設計師引導到模版方法模式上去。