1. 程式人生 > >C#設計模式之十三模板方法模式(Template Method Pattern)【行為型】

C#設計模式之十三模板方法模式(Template Method Pattern)【行為型】

並集 client 變化 args 集中 pac 爸爸 rim 自己

原文:C#設計模式之十三模板方法模式(Template Method Pattern)【行為型】

一、引言

“結構型”的設計模式已經寫完了,從今天我們開始講“行為型”設計模式。現在我們開始講【行為型】設計模式的第一個模式,該模式是【模板方法】,英文名稱是:Template Method Pattern。還是老套路,先從名字上來看看。“模板方法”我第一次看到這個名稱,我的理解是,有一個方法的名字叫“模板方法”,後來深入學習之後,感覺最初的理解還沒錯,也可以換個理解方法,有一個方法包含了一個模板,這個模板是一個算法。在我們的現實生活中有很多例子可以拿來說明這個模式,就拿吃餃子這個事情來說,要想吃到餃子必須經過三步,第一步是“和面”,第二步是“包餡”,第三步是“煮餃子”,這三步就是一個算法,我們要想吃到不同的面和餡的餃子,對這三步中的任意一步就行操作就可以,也可以完全定義這三步,下面我們就來看看這個模式的詳細介紹吧。

二、模板方法模式的詳細介紹



2.1、動機(Motivate)

在軟件構建過程中,對於某一項任務,它常常有穩定的整體操作結構,但各個子步驟卻有很多改變的需求,或者由於固有的原因(比如框架與應用之間的關系)而無法和任務的整體結構同時實現。如何在確定穩定操作結構的前提下,來靈活應對各個子步驟的變化或者晚期實現需求?

2.2、意圖(Intent)

定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。       ——《設計模式》GoF

2.3、結構圖

技術分享圖片

2.4、模式的組成


模板方法模式參與者:

  (1)、抽象類角色(AbstractClass):定義一個模板方法(TemplateMethod),在該方法中包含著一個算法的骨架,具體的算法步驟是PrimitiveOperation1方法和PrimitiveOperation2方法,該抽象類的子類將重定義PrimitiveOperation1和PrimitiveOperation2操作。

  (2)、具體類角色(ConcreteClass):實現PrimitiveOperation1方法和PrimitiveOperation2方法以完成算法中與特定子類(Client)相關的內容。

  在模板方法模式中,AbstractClass中的TemplateMethod提供了一個標準模板,該模板包含PrimitiveOperation1和PrimitiveOperation2兩個方法,這兩個方法的內容Client可以根據自己的需要重寫。

2.5、模板方法模式的具體實現


理解了模板方法的定義之後,自然實現模板方法也不是什麽難事了,下面以生活中吃餃子為例來實現模板方法模式。在現實生活中,做餃子的步驟都大致相同,如果我們針對每種餃子的做法都定義一個類,這樣在每個類中都有很多相同的代碼,為了解決這個問題,我們一般的思路肯定是把相同的部分抽象出來到抽象類中去定義,具體子類來實現具體的不同部分,這個思路也正式模板方法的實現精髓所在,具體實現代碼如下:

  1 namespace 模板方法模式的實現
  2 {
  3     /// <summary>
  4     /// 好吃不如餃子,舒服不如倒著,我最喜歡吃我爸爸包的餃子,今天就拿吃餃子這件事來看看模板方法的實現吧
  5     /// </summary>
  6     class Client
  7     {
  8         static void Main(string[] args)
  9         {
 10             //現在想吃綠色面的,豬肉大蔥餡的餃子
 11             AbstractClass fan = new ConcreteClass();
 12             fan.EatDumplings();
 13 
 14             Console.WriteLine();
 15             //過了段時間,我開始想吃橙色面的,韭菜雞蛋餡的餃子
 16             fan = new ConcreteClass2();
 17             fan.EatDumplings();
 18 
 19 
 20             Console.Read();
 21         }
 22     }
 23 
 24 
 25     //該類型就是抽象類角色--AbstractClass,定義做餃子的算法骨架,這裏有三步驟,當然也可以有多個步驟,根據實際需要而定
 26     public abstract class AbstractClass
 27     {
 28         //該方法就是模板方法,方法裏面包含了做餃子的算法步驟,模板方法可以返回結果,也可以是void類型,視具體情況而定
 29         public void EatDumplings()
 30         {
 31             //和面
 32             MakingDough();
 33             //包餡
 34             MakeDumplings();
 35             //煮餃子
 36             BoiledDumplings();
 37 
 38             Console.WriteLine("餃子真好吃!");
 39         }
 40 
 41         //要想吃餃子第一步肯定是“和面”---該方法相當於算法中的某一步
 42         public abstract void MakingDough();
 43 
 44         //要想吃餃子第二部是“包餃子”---該方法相當於算法中的某一步
 45         public abstract void MakeDumplings();
 46 
 47         //要想吃餃子第三部是“煮餃子”---該方法相當於算法中的某一步
 48         public abstract void BoiledDumplings();
 49     }
 50 
 51     //該類型是具體類角色--ConcreteClass,我想吃綠色面皮,豬肉大蔥餡的餃子
 52     public sealed class ConcreteClass : AbstractClass
 53     {
 54         //要想吃餃子第一步肯定是“和面”---該方法相當於算法中的某一步
 55         public override void MakingDough()
 56         {
 57             //我想要面是綠色的,綠色健康嘛,就可以在此步定制了
 58             Console.WriteLine("在和面的時候加入芹菜汁,和好的面就是綠色的");
 59         }
 60 
 61         //要想吃餃子第二部是“包餃子”---該方法相當於算法中的某一步
 62         public override void MakeDumplings()
 63         {
 64             //我想吃豬肉大蔥餡的,在此步就可以定制了
 65             Console.WriteLine("農家豬肉和農家大蔥,制作成餡");
 66         }
 67 
 68         //要想吃餃子第三部是“煮餃子”---該方法相當於算法中的某一步
 69         public override void BoiledDumplings()
 70         {
 71             //我想吃大鐵鍋煮的餃子,有家的味道,在此步就可以定制了
 72             Console.WriteLine("用我家的大鐵鍋和大木材煮餃子");
 73         }
 74     }
 75 
 76     //該類型是具體類角色--ConcreteClass2,我想吃橙色面皮,韭菜雞蛋餡的餃子
 77     public sealed class ConcreteClass2 : AbstractClass
 78     {
 79         //要想吃餃子第一步肯定是“和面”---該方法相當於算法中的某一步
 80         public override void MakingDough()
 81         {
 82             //我想要面是橙色的,加入胡蘿蔔汁就可以。在此步定制就可以了。
 83             Console.WriteLine("在和面的時候加入胡蘿蔔汁,和好的面就是橙色的");
 84         }
 85 
 86         //要想吃餃子第二部是“包餃子”---該方法相當於算法中的某一步
 87         public override void MakeDumplings()
 88         {
 89             //我想吃韭菜雞蛋餡的,在此步就可以定制了
 90             Console.WriteLine("農家雞蛋和農家韭菜,制作成餡");
 91         }
 92 
 93         //要想吃餃子第三部是“煮餃子”---該方法相當於算法中的某一步
 94         public override void BoiledDumplings()
 95         {
 96             //此處沒要求
 97             Console.WriteLine("可以用一般煤氣和不粘鍋煮就可以");
 98         }
 99     }
100 }

這個模式很簡單,備註也很詳細,看備註應該差不多了,還有一點就是,模板方法裏面的算法步驟,可以有默認實現,也可以沒有實現,在C#裏面可以是抽象方法,當然模板方法也可以有有返回值,也可以沒有返回值。

三、模板方法模式的實現要點:

Template Method模式是一種非常基礎性的設計模式,在面向對象系統中有著大量的應用。它用最簡潔的機制(虛函數的多態性)為很多應用程序框架提供了靈活的擴展,是代碼復用方面的基本實現結構。除了可以靈活應對子步驟的變化外,“Don‘t call me, let me call you(不要調用我,讓我來調用你)”的反向控制結構是Template Method的典型應用。

模板方法模式適用情形:

  (1)、 一次性實現一個算法的不變部分,並將可變的行為留給子類來實現。

  (2)、 各子類中公共的行為應被提取出來並集中到一個公共父類中以避免代碼重復。

  (3)、 控制子類擴展。模板方法只允許在特定點進行擴展,而模板部分則是穩定的。

  模板方法模式特點:

  (1)、 TemplateMethod模式是一種非常基礎性的設計模式,在面向對象系統中大量應用。它用最簡潔的機制(基礎、多態)為很多應用程序框架提供了靈活的擴展點,是代碼復用方面的基本實現結構。

  (2)、 在具體實現方面,被TemplateMethod調用的虛方法可以具有實現,也可以沒有任何實現(抽象方法或虛方法)。但一般推薦將它們設置為protected方法使得只有子類可以訪問它們。

  (3)、 模板方法模式通過對子類的擴展增加新的行為,符合“開閉原則”。


四、.NET 中模板模式的實現

這種模式在控件設計中大量的用到,比如:控件有自己的生命周期,Page對象也有自己的生命周期,Application應用對象也有自己的生命周期,這個生命周期裏面的每個階段其實就是模板方法裏面包含的每個步驟,這些階段步驟會被一個方法包含著,這個方法就是“模板方法”。讓我們再說說控件吧,因為寫好的控件,可能需要被開發人員自定義,那麽在控件裏我們已經定義好了控件呈現、動作的骨架,但是有些自定義的需求,需要延遲到擴展控件的開發人員來決定。  

當我們在做Windows應用程序的時候,就會使用Windows控件,那Windows控件是如何顯示在Windows Form 上的呢,就需要一個OnPaint方法把控件畫出來,這裏OnPaint是一個虛方法的子步驟,這就是一個Template Method設計模式。如果我們不去重寫這個OnPaint方法,它就有一個基本的默認實現,畫一個空窗體。這裏我們並沒有調用OnPaint方法,而是Application的Run會進入Windows的消息循環結構,Paint就是一個消息。當我們移動一下窗口都會導致Paint事件的發生,並導致OnPaint函數的調用,這就是一種反向調用。當然,還有很多其他的子步驟可以提供擴展點,例如OnClose等,很多以On開頭的全部都是Template Method模式的虛方法。 這個裏面內容很復雜,它並不是用一個Template Method在裏面調用所有的子步驟方法,它實際上是把整體的Template Method方法置於了一個消息循環的結構裏面,我們可以把消息循環的結構看做模板方法裏面的TemplateMethod公有非虛方法。

五、總結

今天就到此為止吧,該去做飯了,我們的“模板方法”模式也就寫完了。曾經有一個人,當然也是寫程序的人了說,如果一個人使用面向對象的語言寫程序,但是沒有用過“模板方法”模式,敢肯定這個人寫的程序也絕不是面向對象的,只不過是使用了面向對象的語言而已。雖然有點嚴厲和刻薄,但是不無道理,這個模式很簡單,可能大家在有意或者無意的情況下已經使用過這個模式了,也許只是不知道它的名稱而已。

C#設計模式之十三模板方法模式(Template Method Pattern)【行為型】