重温设计模式(七)——模板方法模式
1. 摘要
在本文中,我们来对模板方法模式做一个全面的了解。
2. 求职简讯
呵呵,如果各位不介意,希望在这里做一个求职的广告。
语言方向:Java/C#/VB,语言熟悉程度降序排列。
求职方向:开发,测试等与IT相关工作。
月薪要求:1000元以上
工作地点:北京
3. 开门见山
模板方法,定义一个操作中算法骨架,而将一些步骤延迟到子类中去实现。
让我们先来看看模板方法的示意图:(引自TerryLee《.NET设计模式——模板方法模式》 )
这个模式的本身很容易理解,我就不自己去画了。
我们看到这个模式的关键在于父类中的TemplateMethod()方法,正是这个方法提供了我们算法的骨架。
曾经和朋友讨论过设计模式,我们一致认为,模板方法是GOF23种设计模式中最重要的设计模式,但是我们却认为,这个模式,我们没有必要把他当作设计模式,甚至可以把他当作一种很普通的继承规则。
4. 模板方法的目的
模板方法的目的,我认为有两点:
A. 减少重复代码。
B. 防止调用出错。
这样的话,我们来归结一下使用模板方法的步骤:
A. 识别
B. 分离
C. 实现
也就是说我们首先要从一系列的算法中去找到,他们的共同部分在哪,称之为识别。然后我们将不同的代码抽取出来,称之为分离。最后,我们用继承将抽取出来的方法在子类中予以实现。
5. 数组操作之模板方法
在一个复杂算法中,总是会在一个算法中牵扯到很多算法的嵌套使用。
例如我们要在一个对象昂数组中,将第N大的对象进行某个操作。那么这样,我们可能要把整个算法分为三步:
A. 将对象数组按照一定规则排序
B. 找出对象数组第N大的数,也许我们不知道这个N,那么这个N可能是从数据库里读,可能是从文件里找,也可能是一个随机数
C. 对这个对象进行某项操作,可能是删除,可能是对这个对象的某个属性做修改。
那么我们需要这样写:
abstract class ArrayOperate { public void TemplateMethod() { List<People> peopleArray = new List<People> { new People("111", 1), new People("222", 2), new People("333", 3), new People("444", 4), new People("555", 5) }; Sort(ref peopleArray); People p = FindOnePeople(peopleArray); p = ChangeSomeoneName(p); Display(p); } public abstract void Sort(ref List<People> peopleArray); public abstract People FindOnePeople(List<People> peopleArray); public abstract People ChangeSomeoneName(People p); public abstract void Display(People p); }class ConOperate:ArrayOperate { public override void Sort(ref List<People> peopleArray) { peopleArray.Sort(SortPeople); } public override People FindOnePeople(List<People> peopleArray) { Random r = new Random(); int randomInt = r.Next(peopleArray.Count); return peopleArray[randomInt]; } public override People ChangeSomeoneName(People p) { p.Name = "123"; return p; } public override void Display(People p) { Console.WriteLine("名字是:"+p.Name + "\n" +"年龄是:"+ p.Age); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); } }class People { private string name; private int age; public string Name { get { return name; } set { name = value; } } public int Age { get { return age; } } public People(string name, int age) { this.name = name; this.age = age; } }class Program { static void Main(string[] args) { ArrayOperate ao = new ConOperate(); ao.TemplateMethod(); } }在这里,我们就实现了方法的可替换。我们在父类中指定了方法的一个执行过程,然后要求子类去实现具体的方法。
6. 模板方法之深究
让我们返回来看:
模板方法的效用一:节省子类代码。
这样,如果我们在父类把方法全部abstract,那么我们就没有做到减少子类代码的作用。
因此,写模板方法很容易,但是写一个好的模板方法就没那么容易了。
我们要做到良好的抽取,把固定的实现方法放在父类方法中去实现。
模板方法效用二:防止调用操作
在很多关于设计模式的书上,都是把父类的模板方法换成了一个简单的方法的堆积,如Display**1,Display*2等等。这样就给很多读者造成了一种错觉,觉得模板方法就是去调用下面的方法,其实并不尽然。
7. 再论模板——泛型
我之前一直说,在很多高级语言,诸如C#,Java中提供了很多实现,这使得很多模式成为了一种思路,而并非必须的实现。
在这个小节,我们来探讨下模板方法的一个思维变种——泛型
在C#2.0中,一个很重要的特性——泛型。我们可能在很多参考书上都会看到这样的说法,C#的泛型,类似于C++的模板。
其实,C#的泛型也是模板方法的一个思维变种,他是对于不同类型都提供了同一套模板去实现。
8. 类爆炸
我们考虑一下这个情况,在模板方法中,如果同一套模板需要多种扩展的话,那是不是又要写很多继承的子类呢?
这样,我又要提到了一个我之前说过无数次的概念:爆炸——这次是类爆炸。
假设我们要对一个数组进行这样或者那样的操作,比如我们要先排序,然后在怎么怎么样。
那我们比如可能要根据数组的不同结构采用不同的排序方式,也许有的用冒泡,有的用快排,有的用归并等等。那么我们就得对每一个排序都写一套对应的子类,好麻烦。
怎么办?怎么办?怎么办?
9. 委托事件改善模板方法
class Program { static void Main(string[] args) { ArrayOperate ao = new ConOperate(); ao.Sort += new SortDelegate( delegate(ref List<People> peopleList) { peopleList.Sort(SortPeople); } ); ao.TemplateMethod(); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); } } public delegate void SortDelegate(ref List<People> list); abstract class ArrayOperate { public event SortDelegate Sort; public void TemplateMethod() { List<People> peopleArray = new List<People> { new People("111", 1), new People("222", 2), new People("333", 3), new People("444", 4), new People("555", 5) }; Sort(ref peopleArray); People p = FindOnePeople(peopleArray); p = ChangeSomeoneName(p); Display(p); } //public abstract void Sort(ref List<People> peopleArray); public abstract People FindOnePeople(List<People> peopleArray); public abstract People ChangeSomeoneName(People p); public abstract void Display(People p); } class ConOperate:ArrayOperate { //public override void Sort(ref List<People> peopleArray) //{ // peopleArray.Sort(SortPeople); //} public override People FindOnePeople(List<People> peopleArray) { Random r = new Random(); int randomInt = r.Next(peopleArray.Count); return peopleArray[randomInt]; } public override People ChangeSomeoneName(People p) { p.Name = "123"; return p; } public override void Display(People p) { Console.WriteLine("名字是:"+p.Name + "\n" +"年龄是:"+ p.Age); } private static int SortPeople(People p1, People p2) { return String.Compare(p1.Name, p2.Name); } } public class People { private string name; private int age; public string Name { get { return name; } set { name = value; } } public int Age { get { return age; } } public People(string name, int age) { this.name = name; this.age = age; } }这样如果再次遭遇我们需要修改排序方式的话,是不是省了很多事呢?
可能这时候有人问,你都能想到的东西,肯定有很多人也想到过!但是这并没有被推广,原因是什么呢?
那就让我们来分析这种方式的特点:
1. 委托其实就是一类方法的模板。
2. 原有的模板方法是将实现对客户隐藏,这样的话,相当于把实现的选择转交给客户去实现。客户去选择模板的具体实现。
10. 委托到底
既然我们都用了委托“改善”模板方法,既然我们已经赋予了客户的实现权利,那么我们试着把权利再给大一些。
很多时候,有这样一种情况,客户可以根据自己去选择方法的实现顺序,那么这个时候,我们就可以用委托去实现。
方法是:我们在一个对客户端公开的类中去实现一些方法,然后我们把这些方法暴露给客户端,让客户端根据自己的选择去组装这些方法。这其实也是个不错的方法。
比如我们有一个类,里面实现了很多的加密算法,然后我们要用一个加密流去对密码进行一个加密过程(注:这里我们不讨论装饰模式)。那么我们就可以让客户根据自己的爱好和解密习惯去选择他们自己的加密顺序。
11. 模板方法宏观化
我们在这里讨论都是在类和接口之间去搞来搞去,其实我们一样可以把模板方法宏观化。
其实我们想一想,设计师,架构师在做一些什么工作呢?模板方法!
他们为我们提供一个软件系统架构的模板,然后让我们去实现具体的细节,就是这样。
因此,我们可以提供一个算法的模板,同样可以提供一个系统过程的模板,提供一个软件的模板。
12. 模式比拼
有些朋友跟我说,不就是规定一个顺序么?这样看来和建造者模式很像啊。
其实不然,他们解决的完全是两种问题:
模板方法是提供一个算法的骨架。
而建造者模式最后完成的是一个对象的组装。
13. 模式总结
模板方法是一个简单但是很重要的模板方法,他最大地解释了继承的威力。
甚至有人说:如果你只懂一个设计模式,那么这个模式就是模板方法模式。
文章至此为止,感谢各位的关注,同时也希望各位多多指教,提出意见,我会改正,让自己越做越好。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!