.NET设计模式-模版方法(Template Method)-阿里云开发者社区

开发者社区> lzhdim> 正文

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

简介: 摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要。   主要内容 1.概述 2.Template Method解说 3..NET中的Template Method模式 4.适用性及实现要点   概述 变化一直以来都是软件设计的永恒话题,在XP编程中提倡拥抱变化,积极应对。
+关注继续查看

摘要: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的实现逻辑:

public abstract class DataAccessObject

img_405b18b4b6584ae338e0f6ecaf736533.gifimg_1c53668bcee393edac0d7b3b3daff1ae.gif
{
    
protected string connectionString;

    
protected DataSet dataSet;

    
public virtual void Connect()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    

        connectionString 
= 

            
"Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";

    }


    
public abstract void Select();

    
public abstract void Display();


    
public virtual void Disconnect()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        connectionString 
= "";
    }


    
// The "Template Method" 

    
public void Run()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        Connect();

        Select();

        Display();

        Disconnect();
    }

}

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

图4

示意性实现代码:

class Categories : DataAccessObject

img_405b18b4b6584ae338e0f6ecaf736533.gifimg_1c53668bcee393edac0d7b3b3daff1ae.gif
{
    
public override void Select()
img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        
string sql = "select CategoryName from Categories";

        SqlDataAdapter dataAdapter 
= new SqlDataAdapter(

            sql, connectionString);

        dataSet 
= new DataSet();

        dataAdapter.Fill(dataSet, 
"Categories");

    }


    
public override void Display()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{

        Console.WriteLine(
"Categories ---- ");

        DataTable dataTable 
= dataSet.Tables["Categories"];

        
foreach (DataRow row in dataTable.Rows)

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif        
{

            Console.WriteLine(row[
"CategoryName"].ToString());

        }


        Console.WriteLine();

    }

}

 

class Products : DataAccessObject

img_405b18b4b6584ae338e0f6ecaf736533.gifimg_1c53668bcee393edac0d7b3b3daff1ae.gif
{
    
public override void Select()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        
string sql = "select top 10 ProductName from Products";

        SqlDataAdapter dataAdapter 
= new SqlDataAdapter(

            sql, connectionString);

        dataSet 
= new DataSet();

        dataAdapter.Fill(dataSet, 
"Products");

    }


    
public override void Display()

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{

        Console.WriteLine(
"Products ---- ");

        DataTable dataTable 
= dataSet.Tables["Products"];

        
foreach (DataRow row in dataTable.Rows)

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif        
{
            Console.WriteLine(row[
"ProductName"].ToString());

        }


        Console.WriteLine();

    }


}

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

public class App

img_405b18b4b6584ae338e0f6ecaf736533.gifimg_1c53668bcee393edac0d7b3b3daff1ae.gif
{
    
static void Main()
img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{

        DataAccessObject dao;


        dao 
= new Categories();

        dao.Run();


        dao 
= new Products();

        dao.Run();

        
// Wait for user 

        Console.Read();

    }


}

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

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,并重写其中部分事件,如下例所示:

public class MyRichTextBox : RichTextBox

img_405b18b4b6584ae338e0f6ecaf736533.gifimg_1c53668bcee393edac0d7b3b3daff1ae.gif
{

    
private static bool m_bPaint = true;

    
private string m_strLine = "";

    
private int m_nContentLength = 0;

    
private int m_nLineLength = 0;

    
private int m_nLineStart = 0;

    
private int m_nLineEnd = 0;

    
private string m_strKeywords = "";

    
private int m_nCurSelection = 0;


    
protected override void OnSelectionChanged(EventArgs e)

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        m_nContentLength 
= this.TextLength;

        
int nCurrentSelectionStart = SelectionStart;

        
int nCurrentSelectionLength = SelectionLength;

        m_bPaint 
= false;

        m_nLineStart 
= nCurrentSelectionStart;

        
while ((m_nLineStart > 0&& (Text[m_nLineStart - 1!= ',')&& (Text[m_nLineStart - 1!= '{')&& (Text[m_nLineStart - 1!= '('))

            m_nLineStart
--;

        m_nLineEnd 
= nCurrentSelectionStart;

        
while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))

            m_nLineEnd
++;


        m_nLineLength 
= m_nLineEnd - m_nLineStart;

        m_strLine 
= Text.Substring(m_nLineStart, m_nLineLength);

        
this.SelectionStart = m_nLineStart;

        
this.SelectionLength = m_nLineLength;


        m_bPaint 
= true;

    }


    
protected override void OnTextChanged(EventArgs e)

img_2887d91d0594ef8793c1db92b8a1d545.gifimg_7a2b9a960ee9a98bfd25d306d55009f8.gif    
{
        
// 重写OnTextChanged
    }

}

其中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模版方法模式(结构型模式)》

作者:TerryLee
出处:http://terrylee.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
冬季实战营第一期学习报告
冬季实战营第一期:从零到一上手玩转云服务器,ECS服务器、Linux
18 0
centos8如何安装java8
centos8如何安装java8
5 0
xshell+阿里云linux+vue+mysql开发练习
这篇文章记录了我在阿里云进行Linux基础学习的过程,以及根据阿里云进行一些简单的开发实践。
7 0
正则表达式基础 | 学习笔记
快速学习正则表达式基础。
5 0
学习报告
本次学习让我收获了很多,了解了ECS服务器。怎么创建ECS服务器,怎么通过公网ip去连接它。还有就是让我快速搭建了一遍LAMP的环境。我一步步安装apache、mysql以及php服务,收获很大。19日的时候我部署了MYSQL数据库服务在ECS服务器上,并通过阿里云的dms控制台去访问我的数据库。对我的帮助很大。1月20日通过直播学习了从0搭建springboot学到了很多。能在我学习的时候快速搭建出来环境。1月21日我动手做了实验,其中安装数据库出现了一些问题关于安装包key的问题。我通取消了gpg检查。成功安装了数据库。最后完成了PolarDB实验。
7 0
Vim 基本操作 | 学习笔记
快速学习 Vim 基本操作。
4 0
文本处理命令 | 学习笔记
快速学习文本处理命令。
5 0
基于阿里云的第一次体验
基于阿里云体验云上操作的乐趣
7 0
+关注
lzhdim
人在20岁以意志力著称,在30岁以智慧取胜,在40岁则靠的是理智的判断。 一个人只有时刻保持幸福快乐的感觉,才会使自己更加热爱生命,热爱生活。只有快乐,愉快的心情,才是创造力和人生动力的源泉;只有不断自己创造快乐,与自己快乐相处的人,才能远离痛苦与烦恼,才能拥有快乐的人生。
522
文章
4
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载