摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要。

主要内容

1.概述

2.Template Method解说

3..NET中的Template Method模式

4.适用性及实现要点

概述

变化一直以来都是软件设计的永恒话题,在XP编程中提倡拥抱变化,积极应对。如何更好的去抓住变化点,应对变化?如何更好的提高代码复用?通过学习Template Method模式,您应该有一个新的认识。

意图

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。[-GOF《设计模式》]

结构图

图1  Template Method 模式结构图

生活中的例子

模板方法定义了一个操作中算法的骨架,而将一些步骤延迟到子类中。房屋建筑师在开发新项目时会使用模板方法。一个典型的规划包括一些建筑平面图,每个平面图体现了不同部分。在一个平面图中,地基、结构、上下水和走线对于每个房间都是一样的。只有在建筑的后期才开始有差别而产生了不同的房屋样式。

图2  使用建筑图为例子的Template Method模式

Template Method模式解说

李建忠老师说过一句话,如果你只想掌握一种设计模式的话,那这个模式一定是Template Method模式。对于这个问题,我想可能是仁者见仁,智者见智,但是有一点不能否认的Template Method模式是非常简单而且几乎是无处不用,很少有人没有用过它。下面我们以一个简单的数据库查询的例子来说明Template Method模式(注意:这个例子在实际数据库开发中并没有任何实际意义,这里仅仅是为了作为示例而已)。

假如我们需要简单的读取Northwind数据库中的表的记录并显示出来。对于数据库操作,我们知道不管读取的是哪张表,它一般都应该经过如下这样的几步:

1.连接数据库(Connect)

2.执行查询命令(Select)

3.显示数据(Display)

4.断开数据库连接(Disconnect)

这些步骤是固定的,但是对于每一张具体的数据表所执行的查询却是不一样的。显然这需要一个抽象角色,给出顶级行为的实现。如下图:

图3

Template Method模式的实现方法是从上到下,我们首先给出顶级框架DataAccessObject的实现逻辑:

  1. public abstract class DataAccessObject
  2.  
  3. {
  4. protected string connectionString;
  5.  
  6. protected DataSet dataSet;
  7.  
  8. public virtual void Connect()
  9.  
  10. {
  11. connectionString =
  12.  
  13. "Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";
  14.  
  15. }
  16.  
  17. public abstract void Select();
  18.  
  19. public abstract void Display();
  20.  
  21. public virtual void Disconnect()
  22.  
  23. {
  24. connectionString = "";
  25. }
  26.  
  27. // The "Template Method"
  28.  
  29. public void Run()
  30.  
  31. {
  32. Connect();
  33.  
  34. Select();
  35.  
  36. Display();
  37.  
  38. Disconnect();
  39. }
  40. }

显然在这个顶级的框架DataAccessObject中给出了固定的轮廓,方法Run()便是模版方法,Template Method模式也由此而得名。而对于Select()和Display()这两个抽象方法则留给具体的子类去实现,如下图:

图4

示意性实现代码:

  1. class Categories : DataAccessObject
  2.  
  3. {
  4. public override void Select()
  5. {
  6. string sql = "select CategoryName from Categories";
  7.  
  8. SqlDataAdapter dataAdapter = new SqlDataAdapter(
  9.  
  10. sql, connectionString);
  11.  
  12. dataSet = new DataSet();
  13.  
  14. dataAdapter.Fill(dataSet, "Categories");
  15.  
  16. }
  17.  
  18. public override void Display()
  19.  
  20. {
  21.  
  22. Console.WriteLine("Categories ---- ");
  23.  
  24. DataTable dataTable = dataSet.Tables["Categories"];
  25.  
  26. foreach (DataRow row in dataTable.Rows)
  27.  
  28. {
  29.  
  30. Console.WriteLine(row["CategoryName"].ToString());
  31.  
  32. }
  33.  
  34. Console.WriteLine();
  35.  
  36. }
  37. }
  1. class Products : DataAccessObject
  2.  
  3. {
  4. public override void Select()
  5.  
  6. {
  7. string sql = "select top 10 ProductName from Products";
  8.  
  9. SqlDataAdapter dataAdapter = new SqlDataAdapter(
  10.  
  11. sql, connectionString);
  12.  
  13. dataSet = new DataSet();
  14.  
  15. dataAdapter.Fill(dataSet, "Products");
  16.  
  17. }
  18.  
  19. public override void Display()
  20.  
  21. {
  22.  
  23. Console.WriteLine("Products ---- ");
  24.  
  25. DataTable dataTable = dataSet.Tables["Products"];
  26.  
  27. foreach (DataRow row in dataTable.Rows)
  28.  
  29. {
  30. Console.WriteLine(row["ProductName"].ToString());
  31.  
  32. }
  33.  
  34. Console.WriteLine();
  35.  
  36. }
  37.  
  38. }

再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:

  1. public class App
  2.  
  3. {
  4. static void Main()
  5. {
  6.  
  7. DataAccessObject dao;
  8.  
  9. dao = new Categories();
  10.  
  11. dao.Run();
  12.  
  13. dao = new Products();
  14.  
  15. dao.Run();
  16.  
  17. // Wait for user
  18.  
  19. Console.Read();
  20.  
  21. }
  22.  
  23. }

在上面的例子中,需要注意的是:

1.对于Connect()和Disconnect()方法实现为了virtual,而Select()和Display()方法则为abstract,这是因为如果这个方法有默认的实现,则实现为virtual,否则为abstract。

2.Run()方法作为一个模版方法,它的一个重要特征是:在基类里定义,而且不能够被派生类更改。有时候它是私有方法(private method),但实际上它经常被声明为protected。它通过调用其它的基类方法(覆写过的)来工作,但它经常是作为初始化过程的一部分被调用的,这样就没必要让客户端程序员能够直接调用它了。

3.在一开始我们提到了不管读的是哪张数据表,它们都有共同的操作步骤,即共同点。因此可以说Template Method模式的一个特征就是剥离共同点。

.NET 中的Template Method模式

.NET Framework中Template Method模式的使用可以说是无处不在,比如说我们需要自定义一个文本控件,会让它继承于RichTextBox,并重写其中部分事件,如下例所示:

  1. public class MyRichTextBox : RichTextBox
  2.  
  3. {
  4.  
  5. private static bool m_bPaint = true;
  6.  
  7. private string m_strLine = "";
  8.  
  9. private int m_nContentLength = ;
  10.  
  11. private int m_nLineLength = ;
  12.  
  13. private int m_nLineStart = ;
  14.  
  15. private int m_nLineEnd = ;
  16.  
  17. private string m_strKeywords = "";
  18.  
  19. private int m_nCurSelection = ;
  20.  
  21. protected override void OnSelectionChanged(EventArgs e)
  22.  
  23. {
  24. m_nContentLength = this.TextLength;
  25.  
  26. int nCurrentSelectionStart = SelectionStart;
  27.  
  28. int nCurrentSelectionLength = SelectionLength;
  29.  
  30. m_bPaint = false;
  31.  
  32. m_nLineStart = nCurrentSelectionStart;
  33.  
  34. while ((m_nLineStart > ) && (Text[m_nLineStart - ] != ',')&& (Text[m_nLineStart - ] != '{')&& (Text[m_nLineStart - ] != '('))
  35.  
  36. m_nLineStart--;
  37.  
  38. m_nLineEnd = nCurrentSelectionStart;
  39.  
  40. while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))
  41.  
  42. m_nLineEnd++;
  43.  
  44. m_nLineLength = m_nLineEnd - m_nLineStart;
  45.  
  46. m_strLine = Text.Substring(m_nLineStart, m_nLineLength);
  47.  
  48. this.SelectionStart = m_nLineStart;
  49.  
  50. this.SelectionLength = m_nLineLength;
  51.  
  52. m_bPaint = true;
  53.  
  54. }
  55.  
  56. protected override void OnTextChanged(EventArgs e)
  57.  
  58. {
  59. // 重写OnTextChanged
  60. }
  61. }

其中OnSelectionChanged()和OnTextChanged()便是Template Method模式中的基本方法之一,也就是子步骤方法,它们的调用已经在RichTextBox中实现了。

实现要点

1.Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

2.除了可以灵活应对子步骤的变化外,“不用调用我,让我来调用你”的反向控制结构是Template Method的典型应用。

3.在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法,纯虚方法),但一般推荐将它们设置为protected方法。[李建忠]

适用性

1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

2.各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

3.控制子类扩展。模板方法只在特定点调用“Hook”操作,这样就只允许在这些点进行扩展。

总结

Template Method模式是非常简单的一种设计模式,但它却是代码复用的一项基本的技术,在类库中尤其重要。

本篇文章写的比较简单,请大家见谅。更多的设计模式文章可以访问《.NET设计模式系列文章》

参考资料

Erich Gamma等,《设计模式:可复用面向对象软件的基础》,机械工业出版社

Robert C.Martin,《敏捷软件开发:原则、模式与实践》,清华大学出版社

阎宏,《Java与模式》,电子工业出版社

Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社

MSDN WebCast 《C#面向对象设计模式纵横谈(14):Template Method模版方法模式(结构型模式)》

NET设计模式 第二部分 行为型模式(15):模版方法模式(Template Method)的更多相关文章

  1. .NET设计模式(16):模版方法(Template Method)(转)

    摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要. 主要内容 1.概述 2.Template Method解说 3..NET中的Te ...

  2. [Python设计模式] 第10章 怎么出试卷?——模版方法模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目 小时候数学老师的随堂测验,都是老师在黑板上写题目,学生在下边抄,然后再做题 ...

  3. 设计模式——模版方法模式详解(论沉迷LOL对学生的危害)

    .  实例介绍 在本例中,我们使用一个常见的场景,我们每个人都上了很多年学,中学大学硕士,有的人天生就是个天才,中学毕业就会微积分,因此得了诺贝尔数学奖:也有的人在大学里学了很多东西,过得很充实很满意 ...

  4. 【设计模式 - 23】之模版方法模式(Template)

    1      模式简介 模版方法模式的定义: 模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤. 模版方法模 ...

  5. 设计模式(java)--模版方法模式之任务分配

    转自:http://blog.csdn.net/zhengzhb/article/details/7405608 定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构 ...

  6. JAVA设计模式(01):创建型-工厂模式【工厂方法模式】(Factory Method)

    简单工厂模式尽管简单,但存在一个非常严重的问题.当系统中须要引入新产品时,因为静态工厂方法通过所传入參数的不同来创建不同的产品,这必然要改动工厂类的源码,将违背"开闭原则".怎样实 ...

  7. 【java设计模式】(10)---模版方法模式(案例解析)

    一.概念 1.概念 模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式. 它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 ...

  8. 设计模式 笔记 模版方法模式 Template Method

    //---------------------------15/04/28---------------------------- //TemplateMethod 模版方法模式----类行为型模式 ...

  9. [Head First设计模式]云南米线馆中的设计模式——模版方法模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

随机推荐

  1. mkdir 获得新建文件权限

    使用mkdir创建文件夹时,发现这个函数有两个参数,第二个参数是为新创建的文件夹指定权限. 但是如果直接用mkdir('文件地址', 0777);时 发现新文件夹的权限并不是777,一般情况下会是02 ...

  2. PHP中session_start 函数详解使用方法

    一.官方 session_status() 返回值为: PHP_SESSION_DISABLED 会话是被禁用的. PHP_SESSION_NONE 会话是启用的,但不存在当前会话. PHP_SESS ...

  3. Day5作业及默写

    1,有如下变量(tu是个元祖),请实现要求的功能 tu = ("alex", [11, 22, {"k1": 'v1', "k2": [&q ...

  4. Python 带有参数的装饰器

    def wrapper_out(flag): # 装饰器本身的参数 def wrapper(fn): # 目标函数 def inner(*args, **kwargs): # 目标函数执行需要的参数 ...

  5. ​Web安全测试解决方案

    Web安全测试解决方案 介绍常见的Web安全风险,Web安全测试方法.测试基本理论和测试过程中的工具引入

  6. 基于链路的OSPFMD5口令认证

    实验要求:掌握基于链路的OSPFMD5口令认证 拓扑如下: 配置如下: R1enable configure terminal interface s0/0/0ip address 192.168.1 ...

  7. [转]Ubuntu安装Python3.6

    Ubuntu安装Python3.6   Ubuntu默认安装了Python2.7和3.5 输入命令python

  8. Maxscale-在第一个节点的配置

    [maxscale]threads=4 ##### Write Service, need to set address[server1]type=serveraddress=172.16.50.36 ...

  9. [LeetCode&Python] Problem 783. Minimum Distance Between BST Nodes

    Given a Binary Search Tree (BST) with the root node root, return the minimum difference between the ...

  10. 各种浏览器兼容trim()的方法

    一.利用while方法解决 function trim(str) { while (str[0] == ' ') { str = str.slice(1); } while (str[str.leng ...