使用 C# (.NET Core) 实现模板方法模式 (Template Method Pattern)
本文的概念内容来自深入浅出设计模式一书.
项目需求
有一家咖啡店, 供应咖啡和茶, 它们的工序如下:
咖啡:
茶:
可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, 另外两步虽然具体内容不一样, 但是都做做的同一类工作.
现在问题也有了, 当前的设计两个类里面有很多重复的代码, 那么应该怎样设计以减少冗余呢?
初次尝试
把共有的方法放到父类里面, 把不同的方法放到子类里面.
父类里面有一个抽象的prepareRecipe()方法[翻译为准备烹饪方法/制作方法], 然后在不同的子类里面有不同的实现. 也就是说每个子类都有自己制作饮料的方法.
再仔细想想应该怎样设计
可以发现两个饮料的制作方法遵循了同样的算法:
把水烧开
用开水冲咖啡或茶
把冲开的饮料放到杯里
添加适当的调料
现在我们来抽像prepareRecipe()方法:
1.先看看两个饮料的差异:
两种饮料都有四道工序, 两个是完全一样的, 另外两个在具体的实现上是略有不同的, 但是还是同样性质的工序.
这两道不同的工序的本质就是冲饮料和添加调料, 所以prepareRecipe()可以这样写:
2. 把上面的方法放到超类里:
这个父类是抽象的, prepareRecipe()将会用来制作咖啡或者茶, 而且我不想让子类去重写这个方法, 因为制作工序(算法)是一定的.
只不过里面的第2部和第4部是需要子类自己来实现的. 所以brew()和addCondiments()是两个抽象的方法, 而另外两个方法则直接在父类里面实现了.
3. 最后茶和咖啡就是这个样子的:
我们做了什么?
我们意识到两种饮料的工序大体是一致的, 尽管某些工序需要不同的实现方法. 所以我们把这些饮料的制作方法归纳到了一个基类CaffeineBeverage里面.
CaffeineBeverage控制着整个工序, 第1, 3部由它自己完成, 第2, 4步则是由具体的饮料子类来完成.
初识模板方法模式
上面的需求种, prepareRecipe() 就是模板方法. 因为, 它首先是一个方法, 然后它还充当了算法模板的角色, 这个需求里, 算法就是制作饮料的整个工序.
所以说: 模板方法定义了一个算法的步骤, 并允许子类提供其中若干个步骤的具体实现.
捋一遍整个流程
1. 我需要做一个茶:
2. 然后调用茶的模板方法:
3. 在模板方法里面执行下列工序:
boildWater();
brew();
pourInCup();
addCondiments();
模板方法有什么好处?
不使用模板方法时:
咖啡和茶各自控制自己的算法.
饮料间的代码重复.
改变算法需要修改多个地方
添加新饮料需要做很多工作.
算法分布在了不同的类里面
使用模板方法后:
CaffeineBeverage这个父类控制并保护算法
父类最大化的代码的复用
算法只在一个地方, 改变算法也只需改变这个地方
新的饮料只需实现部分工序即可
父类掌握着算法, 但是依靠子类去做具体的实现.
模板方法定义
模板方法在一个方法里定义了一套算法的骨架, 算法的某些步骤可以让子类来实现. 模板方法让子类重新定义算法的某些步骤而无需改变算法的结构.
类图:
这个抽象类:
针对这个抽象类, 我们可以有一些扩展:
看这个hook方法, 它是一个具体的方法, 但是啥也没做, 这种就叫做钩子方法. 子类可以重写该方法, 也可以不重写.
模板方法里面的钩子
所谓的钩子, 它是一个在抽象类里面声明的方法, 但是方法里面默认的实现是空的. 这也就给了子类"钩进"算法某个点的能力, 当然子类也可以不这么做, 就看子类是否需要了.
看这个带钩子的饮料父类:
customerWantsCondiments()就是钩子, 子类可以重写它.
在prepareRecipe()方法里面, 通过这个钩子方法的结果来决定是否添加调料.
下面是使用这个钩子的咖啡:
C#代码实现
不带钩子的父类:
using System;
namespace TemplateMethodPattern.Abstractions
{
public abstract class CaffeineBeverage
{
public void PrepareRecipe()
{
BoilWater();
Brew();
PourInCup();
AddCondiments();
}
protected void BoilWater()
{
Console.WriteLine("Boiling water");
}
protected abstract void Brew();
protected void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
protected abstract void AddCondiments();
}
}
咖啡和茶:
using System;
using TemplateMethodPattern.Abstractions;
namespace TemplateMethodPattern.Beverages
{
public class Coffee: CaffeineBeverage
{
protected override void Brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
}
}
using System;
using TemplateMethodPattern.Abstractions;
namespace TemplateMethodPattern.Beverages
{
public class Tea: CaffeineBeverage
{
protected override void Brew()
{
Console.WriteLine("Steeping the tea");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding Lemon");
}
}
}
测试:
var tea = new Tea(); tea.PrepareRecipe();
带钩子的父类:
using System;
namespace TemplateMethodPattern.Abstractions
{
public abstract class CaffeineBeverageWithHook
{
public void PrepareRecipe()
{
BoilWater();
Brew();
PourInCup();
if (CustomerWantsCondiments())
{
AddCondiments();
}
}
protected abstract void Brew();
protected abstract void AddCondiments();
protected void BoilWater()
{
Console.WriteLine("Boiling water");
}
protected void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
public virtual bool CustomerWantsCondiments()
{
return true;
}
}
}
咖啡:
using System;
using TemplateMethodPattern.Abstractions;
namespace TemplateMethodPattern.Beverages
{
public class CoffeeWithHook: CaffeineBeverageWithHook
{
protected override void Brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
protected override void AddCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
public override bool CustomerWantsCondiments()
{
var answer = GetUserInput();
if (answer == "yes")
{
return true;
}
return false;
}
private string GetUserInput()
{
Console.WriteLine("Would you like milk and sugar with you coffee (y/n) ?");
var keyInfo = Console.ReadKey();
return keyInfo.KeyChar == 'y' ? "yes" : "no";
}
}
}
测试:
static void MakeCoffeeWithHook()
{
var coffeeWithHook = new CoffeeWithHook();
Console.WriteLine("Making coffee...");
coffeeWithHook.PrepareRecipe();
}
钩子和抽象方法的区别?
抽象方法是算法里面必须要实现的一个方法或步骤, 而钩子是可选实现的.
好莱坞设计原则
好莱坞设计原则就是: 别给我们打电话, 我们会给你打电话.
好莱坞原则可以防止依赖关系腐烂. 依赖关系腐烂是指高级别的组件依赖于低级别的组件, 它又依赖于高级别组件, 它又依赖于横向组件, 又依赖于低级别组件....以此类推. 当腐烂发生的时候, 没人会看懂你的系统是怎么设计的.
而使用好莱坞原则, 我们可以让低级别组件钩进一个系统, 但是高级别组件决定何时并且以哪种方式它们才会被需要. 换句话说就是, 高级别组件对低级别组件说: "别给我们打电话, 我们给你们打电话".
好莱坞原则和模板方法模式
模板方法里, 父类控制算法, 并在需要的时候调用子类的方法.
而子类从来不会直接主动调用父类的方法.
其他问题
好莱坞原则和依赖反转原则DIP的的区别?
DIP告诉我们不要使用具体的类, 尽量使用抽象类. 而好莱坞原则则是让低级别组件可以被钩进算法中去, 也没有建立低级别组件和高级别组件间的依赖关系.
三种模式比较:
模板方法模式: 子类决定如何实现算法中特定的步骤
策略模式: 封装变化的行为并使用委托来决定哪个行为被使用.
工厂方法模式: 子类决定实例化哪个具体的类.
使用模板方法做排序
看看java里面数组的排序方法:
mergeSort就可以看做事模板方法, compareTo()就是需要具体实现的方法.
但是这个并没有使用子类, 但是根据实际情况, 还是可以灵活使用的, 你需要做的就是实现Comparable接口即可., 这个接口里面只有一个CompareTo()方法.
具体使用C#就是这样:
鸭子:
using System;
namespace TemplateMethodPattern.ForArraySort
{
public class Duck : IComparable
{
private readonly string _name;
private readonly int _weight;
public Duck(string name, int weight)
{
_name = name;
_weight = weight;
}
public override string ToString()
{
return $"{_name} weights {_weight}";
}
public int CompareTo(object obj)
{
if (obj is Duck otherDuck)
{
if (_weight < otherDuck._weight)
{
return -1;
}
if (_weight == otherDuck._weight)
{
return 0;
}
}
return 1;
}
}
}
比较鸭子:
static void SortDuck()
{
var ducks = new Duck[]
{
new Duck("Duffy", 8),
new Duck("Dewey", 2),
new Duck("Howard", 7),
new Duck("Louie", 2),
new Duck("Donal", 10),
new Duck("Huey", 3)
};
Console.WriteLine("Before sorting:");
DisplayDucks(ducks);
Array.Sort(ducks);
Console.WriteLine();
Console.WriteLine("After sorting:");
DisplayDucks(ducks);
}
private static void DisplayDucks(Duck[] ducks)
{
foreach (Duck t in ducks)
{
Console.WriteLine(t);
}
}
效果:
其他钩子例子
java的JFrame:
JFrame父类里面有一个update()方法, 它控制着算法, 我们可以使用paint()方法来钩进到该算法的那部分.
父类里面JFrame的paint()啥也没做, 就是个钩子, 我们可以在子类里面重写paint(), 上面例子的效果就是:
另一个例子Applet小程序:
这5个方法全是重写的钩子...
我没看过winform或者wpf/sl的源码, 我估计也应该有一些钩子吧.
总结
好莱坞原则: "别给我们打电话, 我们给你打电话"
模板方法模式: 模板方法在一个方法里定义了一套算法的骨架, 算法的某些步骤可以让子类来实现. 模板方法让子类重新定义算法的某些步骤而无需改变算法的结构
该系列的源码: https://github.com/solenovex/Head-First-Design-Patterns-in-CSharp
相关文章:
C# 观察者模式 以及 delegate 和 event
用C# (.NET Core) 实现抽象工厂设计模式
使用 C# (.NET Core) 实现模板方法模式 (Template Method Pattern)相关推荐
- 设计模式之模板方法模式(Template Method Pattern)
模式的定义与特点 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤.它 ...
- 模板方法模式(Template Method Pattern)学习笔记
模板方法模式可以所是最为常见的一种设计模式了,出乎很多人意料的是,很多人已经在他们的代码中用到了模板方法模式而没有意识到自己用到了这个模式,模板方法模式几乎可以在所有的抽象基类中找到. 通过模板方法模 ...
- 模板方法模式(Template Method Pattern)
模板方法模式: (就是在抽象基类中定一个复用的方法!) 在一个方法中定一个算法的骨架,而将一些步骤延迟到子类中. 模板方法使得子类可以再不改变算法结构的情况下,重新定义算法中的某些步骤. 实例: 泡茶 ...
- Net设计模式实例之模板方法模式(Template Mothed Pattern)(1)
一.模板方法模式简介(Brief Introduction) 模板方法模式(Template Method Pattern),定义一个操作中的算法骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不 ...
- 模板方法模式 Template method 行为型 设计模式(二十六)
模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为你的 ...
- JAVA设计模式(14) —行为型模板方法模式(Template Method)
1 定义: 模板方法模式(Template Method) Define the skeleton of an algorithm in anoperation, deferring some ste ...
- java 模板方法_设计模式(java实现)_模板方法模式(Template method)
设计模式(java实现)_模板方法模式(Template method) 模板方法模式是编程中经常用到到的模式.它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现.这样,新的子类可以在不改变一个 ...
- 设计模式之模板方法模式(Template Method)摘录
23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...
- [设计模式-行为型]模板方法模式(Template Method)
一句话 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中. 概括 解析 看过<如何说服女生上床>这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇.打破僵局.展开追求.接吻.前戏.动 ...
最新文章
- ML基石_2_LearnAnswer2
- 理财工具——七大标准比率
- linux命令录制,怎样在Linux上录制你的终端操作及回放
- spring security默认登录页面登录用户,和自定义数据源
- CodeForces - 618D Hamiltonian Spanning Tree(思维+贪心)
- (107)FPGA面试题-Verilog编写200ns异步/同步低有效复位激励
- asp.net web开发框架_Python之Web开发框架学习 发送电子邮件
- 软件测试—软件测试基础知识—测试用例设计的方法之场景法、正交试验法和错误推断法
- python语言-Python语言介绍
- linux小程序实验报告,linux 小程序分析
- php 清除浮动,清除浮动的几种方法
- [论文评析]ArXiv,2021, CrossFormer技术分析
- 所谓的开发是java还是PLC_SCL是否将成为PLC的主流编程语言?
- php华文行楷,css设置中文字体
- Silvaco 安装问题
- 【渝粤教育】广东开放大学 开放教育 学生创业案例 形成性考核 (59)
- Magic Firewall 简介
- IGRP和EIGRP为什么是距离矢量协议
- 6. 利用word的替换功能可以完成很多工作——word高级替换技巧
- 【CHATGPT-3.5】如何使用ChatGPT的同时并学习记忆
热门文章
- RabbitMQ详解(三)
- 装饰一个类及内部方法
- Android百度地图开发01之初体验
- 最具体的历史centos下一个 postfix + extmail + dovecot + maildrop 安装注意事项2014更新...
- 在不同的ObjectContext中更新数据
- ABP vNext微服务架构详细教程——简介
- 拥抱开源!除了微软红帽,这些国际大厂你认识几个?
- 【DotNetMLLearn】.NET Core人工智能系列-概述
- [翻译]Go与C#的比较,第二篇:垃圾回收
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十)——一步一步教你如何撸Dapr之绑定...