本文的概念内容来自深入浅出设计模式一书.

项目需求

有一家咖啡店, 供应咖啡和茶, 它们的工序如下:

咖啡:

茶:

可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, 另外两步虽然具体内容不一样, 但是都做做的同一类工作.

现在问题也有了, 当前的设计两个类里面有很多重复的代码, 那么应该怎样设计以减少冗余呢?

初次尝试

把共有的方法放到父类里面, 把不同的方法放到子类里面.

父类里面有一个抽象的prepareRecipe()方法[翻译为准备烹饪方法/制作方法], 然后在不同的子类里面有不同的实现. 也就是说每个子类都有自己制作饮料的方法.

再仔细想想应该怎样设计

可以发现两个饮料的制作方法遵循了同样的算法:

  1. 把水烧开
  2. 用开水冲咖啡或茶
  3. 把冲开的饮料放到杯里
  4. 添加适当的调料

现在我们来抽像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 -;
}
if (_weight == otherDuck._weight)
{
return ;
}
}
return ;
}
}
}

比较鸭子:

        static void SortDuck()
{
var ducks = new Duck[]
{
new Duck("Duffy", ),
new Duck("Dewey", ),
new Duck("Howard", ),
new Duck("Louie", ),
new Duck("Donal", ),
new Duck("Huey", )
};
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# (.NET Core) 实现模板方法模式 (Template Method Pattern)的更多相关文章

  1. 使用C# (.NET Core) 实现模板方法模式 (Template Method Pattern)

    本文的概念内容来自深入浅出设计模式一书. 项目需求 有一家咖啡店, 供应咖啡和茶, 它们的工序如下: 咖啡: 茶: 可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, ...

  2. 设计模式 - 模板方法模式(template method pattern) JFrame 具体解释

    模板方法模式(template method pattern) JFrame 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考模板方法模式(templ ...

  3. 乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern)

    原文:乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 模板方法模式(Template Method ...

  4. 设计模式 - 模板方法模式(template method pattern) 排序(sort) 具体解释

    模板方法模式(template method pattern) 排序(sort) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考模板方法模式(tem ...

  5. 设计模式 - 模板方法模式(template method pattern) 具体解释

    模板方法模式(template method pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 模板方法模式(template metho ...

  6. 二十四种设计模式:模板方法模式(Template Method Pattern)

    模板方法模式(Template Method Pattern) 介绍定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.Template Method使得子类可以不改变一个算法的结构即可重定义该算法 ...

  7. 模板方法模式(Template Method Pattern)——复杂流程步骤的设计

    模式概述 在现实生活中,很多事情都包含几个实现步骤,例如请客吃饭,无论吃什么,一般都包含点单.吃东西.买单等几个步骤,通常情况下这几个步骤的次序是:点单 --> 吃东西 --> 买单. 在 ...

  8. 设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

    今天是五.四青年节,祝大家节日快乐.看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火.这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知 ...

  9. 模板方法模式(Template Method Pattern)

    模板方法模式是一种基于继承的代码复用技术,定义一个操作中的算法的骨架,而将步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤. 模式中的角色 抽象类(Abstrac ...

随机推荐

  1. Guava常用方法

    简介 Guava,中文是石榴的意思,Guava项目,是基于java的Google的开源的工具类库,包含了许多被Google的java项目广泛依赖的核心库, 例如:集合.缓存.原生类型支持.并发库.通用 ...

  2. 将 Shiro 作为应用的权限基础 三:基于注解实现的授权认证过程

    授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限. 如,判断一个用户有查看页面的权限,编辑数据的权限,拥有某一按钮的权限等等. 一.用户权限模型 为实现一个较为灵活的用户权限数据模 ...

  3. (Matlab)GPU计算所需的配置

    电脑:联想扬天 M4400 系统:win 7 X64 硬件:NVIDIA GeForce GT 740M 独显2G     硬件驱动: 软件: Matlab 2015a   %需要安装 Paralle ...

  4. python 函数基础 定义

    一.函数介绍 1.为什么要有函数? 没有函数的代码组织结构不清晰,可读性差. 代码冗余 管理维护难度大,扩展性 2.什么是函数? 具备某一个功能的工具就是程序中的函数. 事先准备工具的过程就是:函数的 ...

  5. 攻防组网之—-MikroTik软路由的配置和FUZZ

    作者:红发香克斯 开篇之前一直想借助一台高性能PC利用VMWare组建一个攻防网络,之前有一贴写过找到了一个软路由系统,就是可以在VMWare中使用的路由器操作系统,那个系统还挺常用,上次帖子写到了安 ...

  6. Java基础学习笔记二十五 MySQL

    MySQL 在dos中操作mysql 连接mysql命令: mysql -uroot -p密码 ,连接OK,会出现mysql> 对数据库的操作 创建一个库 create database 库名 ...

  7. 第2次作业:Wechat创作史

    Wechat创作史   比尔盖茨曾经说过一句话:21世纪要么电子商务,要么无商可务. 2.1 介绍产品相关信息 -the information about Wechat 你选择的产品是? 选择微信作 ...

  8. iOS开发-继承特征详解

    面向对象的三大特性:封装,继承,多态. 1.继承 继承既能保证类的完整,又能简化代码. 把公共的方法和实例变量写在子类,子类只需要写父类独有的实例变量和方法即可. 继承是面向对象三大特性之一,合理的继 ...

  9. nyoj 背包问题

    背包问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 现在有很多物品(它们是可以分割的),我们知道它们每个物品的单位重量的价值v和重量w(1<=v,w< ...

  10. Sudoku 第一步

    看到这个问题的思路是先解决生成数独生成器的编写,然后再解决数独求解的问题.最开始第一想法就是暴力求解,仔细算一下复杂度,发现这肯定耗时很久,于是看了很多博客(见转载).我们用回溯搜出来正解.