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

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

 

主要内容

1.概述

2Template Method解说

3.NET中的Template Method模式

4.适用性及实现要点

 

概述

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

意图

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

结构图

1  Template Method 模式结构图

 

生活中的例子

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

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

Template Method模式解说

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

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

1.连接数据库(Connect

2.执行查询命令(Select

3.显示数据(Display

4.断开数据库连接(Disconnect

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

3

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

None.gifpublic abstract class DataAccessObject
None.gif
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
protected string connectionString;
InBlock.gif
InBlock.gif    
protected DataSet dataSet;
InBlock.gif
InBlock.gif    
public virtual void Connect()
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        connectionString 
= "";
ExpandedSubBlockEnd.gif    }

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

InBlock.gif    
public void Run()
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.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.gifclass Categories : DataAccessObject
None.gif
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public override void Select()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif        
dot.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.gifclass Products : DataAccessObject
None.gif
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public override void Select()
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif        
dot.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.gifpublic class App
None.gif
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
static void Main()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.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

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

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

.NET 中的Template Method模式

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

None.gifpublic class MyRichTextBox : RichTextBox
None.gif
ExpandedBlockStart.gifContractedBlock.gif
dot.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.gifContractedSubBlock.gif    
dot.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.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// 重写OnTextChanged
ExpandedSubBlockEnd.gif
    }

ExpandedBlockEnd.gif}

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

实现要点

1Template 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模版方法模式(结构型模式)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300401.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学界最恐怖的存在,学过数学的人,一辈子都不会忘记!

全世界只有3.14 % 的人关注了青少年数学之旅何谓数学&#xff1f;数学家Eduardo曾这样回答“数学是永恒&#xff0c;是真理&#xff0c;是一切的答案。”回首往昔数学始终伴随我们左右纵横交错的几何、繁琐复杂的运算难以求解的方程、无从下手的猜想......尽管在数学道路上有多…

把我的爱送给你――C#3.5(这题目似乎写错了)

本文发表于 中国IT实验室周报。这是我初次写给杂志社稿件&#xff0c;其中必有许多不足之处&#xff0c;还望大家见谅&#xff0c;虽然这篇文章质量可能不太高&#xff0c;但我希望能将个人的这些总结与大家分享。 从笔者接触编程至今&#xff0c;经历了数种编程语言&#xff0…

Blazor 数据绑定开发指南

翻译自 Waqas Anwar 2021年3月21日的文章 《A Developer’s Guide to Blazor Data Binding》 [1]现如今&#xff0c;大多数 Web 应用程序要么是在页面上显示某种数据&#xff0c;要么是使用表单从用户那里收集数据。这意味着每个 SPA 框架都必须支持数据绑定&#xff0c;以便开…

葛优:你们有看过我的作品吗?| 今日趣图

全世界只有3.14 % 的人关注了青少年数学之旅

阳江海滩景色一瞥

前几天同学去阳江的海边玩了2天&#xff0c;海景相当不错&#xff0c;不敢独享&#xff0c;特来贴图。 转载于:https://www.cnblogs.com/absolute8511/archive/2008/05/24/1649638.html

使用表达式自定义Serilog输出格式

Serilog是.NET Core中常用的结构化日志类库&#xff0c;透过logging API可以轻松的记录应用程式中对象属性&#xff0c;方便快速进行logging内容进行查询与分析&#xff0c;并将其记录内容通过指定方式输出。今天&#xff0c;介绍一个Nuget包Serilog.Expressions&#xff0c;它…

那些不回微信的人,都在看什么?

全世界只有3.14 % 的人关注了青少年数学之旅有人统计过&#xff0c;我们平均每天花在看内容上的时间是5-6小时&#xff0c;只要拥有一个手机&#xff0c;我们似乎无所不知。但&#xff0c;你是否还记得昨天都看了哪些内容&#xff1f;你能将它们一一都说出个大概吗&#xff1f;…

怎样让WinForms下DataGrid可以像ASP.NET下的DataGrid一样使用自定义的模板列

昨天被问到一个问题&#xff1a;怎么把WinForms里的DataGrid的绑定了数据库bit字段的列默认显示的CheckBox换成“男”和“女”&#xff0c;也就是说怎么样像ASP.NET的模板列那样可以自定义。&#xff08;此处不考虑在SQL在用Case把数据结果转换了&#xff09;由于&#xff0c;基…

智能优化算法应用:基于原子轨道搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于原子轨道搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于原子轨道搜索算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.原子轨道搜索算法4.实验参数设定…

dotnet中的counters说明(三)

本篇分别说明一下System.Net下的Http计数器&#xff0c;NameResolution计数器&#xff0c;Security计数器和Sockets计数器。同时&#xff0c;下面指标各项()里的项目是--counters 参数[]里的项&#xff0c;用逗号分隔多项指标。System.Net.Http计数器以下计数器由 HTTP 堆栈发布…

c#开发-基础知识及有用技巧(一)

1、时间长度的计算 TimeSpan类。例如&#xff1a;TimeSpan span dateTime1 - dateTime2 方便啊2、从类&#xff08;Class)返回一个System.Type类型&#xff0c;用typeof关键字3、从一个对象实例(Object)返回一个System.Type类型&#xff0c;用GetType方法4、判断是否处于设计…

天呐!你知道MSBuild都干了些什么

一个典型的.NET5.0项目文件是这样的&#xff0c;看着非常简洁&#xff1a;<Project Sdk"Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>net5.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference I…

为什么理工类专业成绩好的人,英语总是很差?

▲ 点击查看在知乎上曾经有一个话题&#xff1a;为什么会有数学很好但英语很差的人&#xff1f;这个话题还被浏览了四十多万次。说起这个话题&#xff0c;评论中很多人也纷纷表示感同身受。在上学的时候&#xff0c;要么英语成绩好到飞起&#xff0c;要么数学成绩牛逼到不行。…

.Net Core with 微服务 - Polly 服务降级熔断

在我们实施微服务之后&#xff0c;服务间的调用变的异常频繁。多个服务之间可能是互相依赖的关系。某个服务出现故障或者是服务间的网络出现故障都会造成服务调用的失败&#xff0c;进而影响到某个业务服务处理失败。某一个服务调用失败轻则造成当前相关业务无法处理&#xff1…

IfElseActivity

IfElseActivity 1.IfElseActivity有两个IfElseBranch子控件&#xff0c;分别作为IfElse的两个分支容器,系统自动添加&#xff0c; 2.其中左边(为真件条)的IfElseBranch容器要设Condition 3.IfElse左边(为真件条)的IfElseBranch容器的Condition有两个条件模式:Code Condition,De…

荷兰人发明的新客机是劈叉的!乘客坐在机翼上

全世界只有3.14 % 的人关注了青少年数学之旅与汽车外型的复杂多变相比&#xff0c;飞机的外型似乎总是那么朴实无华&#xff0c;不管是客机还是战斗机&#xff0c;大约都是大家习以为常的那个样子……但是&#xff0c;终于有人要推陈出新了&#xff01;荷兰皇家航空公司与代尔夫…

预约 .NET Conf: Focus on F# 活动,赢得官方周边!

James: 最近 .NET 基金会预告了将在本月29日底举行的 .NET Conf: Focus on F# 线上活动&#xff0c;预约这次活动还能有机会赢得官方大礼包。.NET Conf: Focus on F# 是一个免费的、为期一天的直播活动&#xff0c;会上有来自社区和使用f#语言的微软团队的演讲者。学习 F# 如何…

.NET Day in China(上海-今日活动)| 线上线下

点击蓝字关注我们活动简介.NET 6 Preview 6 在 7月14日已经发布&#xff0c;.NET 6 是微软开启全平台统一一个 .NET 计划以来的第一个 LTS 版本&#xff0c;意义重大&#xff0c;微软在 .NET 6 引入了 MAUI&#xff0c;跨平台开发将更为简单&#xff0c;ASP.NET Core 也在不断的…

公司重金求数据分析师:为什么90%的公司都需要它?

全世界只有3.14 % 的人关注了青少年数学之旅混迹互联网的同学们&#xff0c;或多或少都对“数据分析师”这一职业有所耳闻。即使你不认识任何数据分析师&#xff0c;也一定看到过这类研究报告或者文章&#xff1a;Smart is the new sexy. 酷炫的图表&#xff0c;理性的分析阐述…

php配置问题汇总

前两天开始跟进PHP&#xff1b;我觉得&#xff0c;PHP的环境配置远比其他语言的要复杂很多。我所说的“其他语言”&#xff0c;包括Java&#xff0c;Oracle&#xff0c;scala&#xff0c;Python等。到现在PHP的环境被搭好&#xff0c;因为是全手动的配置&#xff0c;我完完整整…