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

简介:
摘要: 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的实现逻辑:
None.gif public   abstract   class  DataAccessObject
None.gif
ExpandedBlockStart.gif
{
InBlock.gif    
protected string connectionString;
InBlock.gif
InBlock.gif    
protected DataSet dataSet;
InBlock.gif
InBlock.gif    
public virtual void Connect()
InBlock.gif
ExpandedSubBlockStart.gif    

InBlock.gif        connectionString 
= 
InBlock.gif
InBlock.gif            
"Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public abstract void Select();
InBlock.gif
InBlock.gif    
public abstract void Display();
InBlock.gif
InBlock.gif
InBlock.gif    
public virtual void Disconnect()
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif        connectionString 
= "";
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// The "Template Method" 
InBlock.gif

InBlock.gif    
public void Run()
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif        Connect();
InBlock.gif
InBlock.gif        Select();
InBlock.gif
InBlock.gif        Display();
InBlock.gif
InBlock.gif        Disconnect();
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
显然在这个顶级的框架 DataAccessObject中给出了固定的轮廓,方法 Run()便是模版方法, Template Method模式也由此而得名。而对于 Select()Display()这两个抽象方法则留给具体的子类去实现,如下图:
4
示意性实现代码:
None.gif class  Categories : DataAccessObject
None.gif
ExpandedBlockStart.gif
{
InBlock.gif    
public override void Select()
ExpandedSubBlockStart.gif    
{
InBlock.gif        
string sql = "select CategoryName from Categories";
InBlock.gif
InBlock.gif        SqlDataAdapter dataAdapter 
= new SqlDataAdapter(
InBlock.gif
InBlock.gif            sql, connectionString);
InBlock.gif
InBlock.gif        dataSet 
= new DataSet();
InBlock.gif
InBlock.gif        dataAdapter.Fill(dataSet, 
"Categories");
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public override void Display()
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif
InBlock.gif        Console.WriteLine(
"Categories ---- ");
InBlock.gif
InBlock.gif        DataTable dataTable 
= dataSet.Tables["Categories"];
InBlock.gif
InBlock.gif        
foreach (DataRow row in dataTable.Rows)
InBlock.gif
ExpandedSubBlockStart.gif        
{
InBlock.gif
InBlock.gif            Console.WriteLine(row[
"CategoryName"].ToString());
InBlock.gif
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        Console.WriteLine();
InBlock.gif
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
 
None.gif class  Products : DataAccessObject
None.gif
ExpandedBlockStart.gif
{
InBlock.gif    
public override void Select()
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif        
string sql = "select top 10 ProductName from Products";
InBlock.gif
InBlock.gif        SqlDataAdapter dataAdapter 
= new SqlDataAdapter(
InBlock.gif
InBlock.gif            sql, connectionString);
InBlock.gif
InBlock.gif        dataSet 
= new DataSet();
InBlock.gif
InBlock.gif        dataAdapter.Fill(dataSet, 
"Products");
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public override void Display()
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif
InBlock.gif        Console.WriteLine(
"Products ---- ");
InBlock.gif
InBlock.gif        DataTable dataTable 
= dataSet.Tables["Products"];
InBlock.gif
InBlock.gif        
foreach (DataRow row in dataTable.Rows)
InBlock.gif
ExpandedSubBlockStart.gif        
{
InBlock.gif            Console.WriteLine(row[
"ProductName"].ToString());
InBlock.gif
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        Console.WriteLine();
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
ExpandedBlockEnd.gif}
再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:
None.gif public   class  App
None.gif
ExpandedBlockStart.gif
{
InBlock.gif    
static void Main()
ExpandedSubBlockStart.gif    
{
InBlock.gif
InBlock.gif        DataAccessObject dao;
InBlock.gif
InBlock.gif
InBlock.gif        dao 
= new Categories();
InBlock.gif
InBlock.gif        dao.Run();
InBlock.gif
InBlock.gif
InBlock.gif        dao 
= new Products();
InBlock.gif
InBlock.gif        dao.Run();
InBlock.gif
InBlock.gif        
// Wait for user 
InBlock.gif

InBlock.gif        Console.Read();
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
ExpandedBlockEnd.gif}
在上面的例子中,需要注意的是:
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,并重写其中部分事件,如下例所示:
None.gif public   class  MyRichTextBox : RichTextBox
None.gif
ExpandedBlockStart.gif
{
InBlock.gif
InBlock.gif    
private static bool m_bPaint = true;
InBlock.gif
InBlock.gif    
private string m_strLine = "";
InBlock.gif
InBlock.gif    
private int m_nContentLength = 0;
InBlock.gif
InBlock.gif    
private int m_nLineLength = 0;
InBlock.gif
InBlock.gif    
private int m_nLineStart = 0;
InBlock.gif
InBlock.gif    
private int m_nLineEnd = 0;
InBlock.gif
InBlock.gif    
private string m_strKeywords = "";
InBlock.gif
InBlock.gif    
private int m_nCurSelection = 0;
InBlock.gif
InBlock.gif
InBlock.gif    
protected override void OnSelectionChanged(EventArgs e)
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif        m_nContentLength 
= this.TextLength;
InBlock.gif
InBlock.gif        
int nCurrentSelectionStart = SelectionStart;
InBlock.gif
InBlock.gif        
int nCurrentSelectionLength = SelectionLength;
InBlock.gif
InBlock.gif        m_bPaint 
= false;
InBlock.gif
InBlock.gif        m_nLineStart 
= nCurrentSelectionStart;
InBlock.gif
InBlock.gif        
while ((m_nLineStart > 0&& (Text[m_nLineStart - 1!= ',')&& (Text[m_nLineStart - 1!= '{')&& (Text[m_nLineStart - 1!= '('))
InBlock.gif
InBlock.gif            m_nLineStart
--;
InBlock.gif
InBlock.gif        m_nLineEnd 
= nCurrentSelectionStart;
InBlock.gif
InBlock.gif        
while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))
InBlock.gif
InBlock.gif            m_nLineEnd
++;
InBlock.gif
InBlock.gif
InBlock.gif        m_nLineLength 
= m_nLineEnd - m_nLineStart;
InBlock.gif
InBlock.gif        m_strLine 
= Text.Substring(m_nLineStart, m_nLineLength);
InBlock.gif
InBlock.gif        
this.SelectionStart = m_nLineStart;
InBlock.gif
InBlock.gif        
this.SelectionLength = m_nLineLength;
InBlock.gif
InBlock.gif
InBlock.gif        m_bPaint 
= true;
InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
protected override void OnTextChanged(EventArgs e)
InBlock.gif
ExpandedSubBlockStart.gif    
{
InBlock.gif        
// 重写OnTextChanged
ExpandedSubBlockEnd.gif
    }

ExpandedBlockEnd.gif}
其中 OnSelectionChanged()OnTextChanged()便是 Template Method模式中的基本方法之一,也就是子步骤方法,它们的调用已经在 RichTextBox中实现了。
实现要点
1 Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
2 .除了可以灵活应对子步骤的变化外,“不用调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
3 .在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法,纯虚方法),但一般推荐将它们设置为protected方法。[李建忠]
适用性
1 .一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2 .各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是OpdykeJohnson所描述过的“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
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模版方法模式(结构型模式)













本文转自lihuijun51CTO博客,原文链接: http://blog.51cto.com/terrylee/67766  ,如需转载请自行联系原作者
相关文章
|
28天前
|
SQL 缓存 开发框架
分享一个 .NET EF6 应用二级缓存提高性能的方法
分享一个 .NET EF6 应用二级缓存提高性能的方法
|
28天前
|
程序员 数据库
分享 2 个 .NET EF 6 只更新某些字段的方法
分享 2 个 .NET EF 6 只更新某些字段的方法
|
28天前
|
数据库
分享一个 .NET EF 6 扩展 Where 的方法
分享一个 .NET EF 6 扩展 Where 的方法
|
28天前
|
开发框架 前端开发 算法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法
|
28天前
|
开发框架 中间件 .NET
分享 ASP.NET Core Web Api 中间件获取 Request Body 两个方法
分享 ASP.NET Core Web Api 中间件获取 Request Body 两个方法
|
28天前
|
开发框架 .NET API
如何在 ASP.NET Core Web API 方法执行前后 “偷偷“ 作一些 “坏“ 事?初识 ActionFilterAttribute
如何在 ASP.NET Core Web API 方法执行前后 “偷偷“ 作一些 “坏“ 事?初识 ActionFilterAttribute
|
28天前
|
开发框架 前端开发 .NET
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
|
5天前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
18 7
|
4天前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
13 0
|
28天前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
29 0