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

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

项目需求

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

640?wx_fmt=png&wxfrom=5&wx_lazy=1

咖啡:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

茶:

640?wx_fmt=png

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

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

初次尝试

640?wx_fmt=png

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

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

再仔细想想应该怎样设计

640?wx_fmt=png

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

  1. 把水烧开

  2. 用开水冲咖啡或茶

  3. 把冲开的饮料放到杯里

  4. 添加适当的调料

现在我们来抽像prepareRecipe()方法:

1.先看看两个饮料的差异:

640?wx_fmt=png

两种饮料都有四道工序, 两个是完全一样的, 另外两个在具体的实现上是略有不同的, 但是还是同样性质的工序.

这两道不同的工序的本质就是冲饮料和添加调料, 所以prepareRecipe()可以这样写:

640?wx_fmt=png

2. 把上面的方法放到超类里:

640?wx_fmt=png

这个父类是抽象的, prepareRecipe()将会用来制作咖啡或者茶, 而且我不想让子类去重写这个方法, 因为制作工序(算法)是一定的.

只不过里面的第2部和第4部是需要子类自己来实现的. 所以brew()和addCondiments()是两个抽象的方法, 而另外两个方法则直接在父类里面实现了.

3. 最后茶和咖啡就是这个样子的:

640?wx_fmt=png

 

640?wx_fmt=png

我们做了什么?

我们意识到两种饮料的工序大体是一致的, 尽管某些工序需要不同的实现方法. 所以我们把这些饮料的制作方法归纳到了一个基类CaffeineBeverage里面.

CaffeineBeverage控制着整个工序, 第1, 3部由它自己完成, 第2, 4步则是由具体的饮料子类来完成.

初识模板方法模式

640?wx_fmt=png

上面的需求种, prepareRecipe() 就是模板方法. 因为, 它首先是一个方法, 然后它还充当了算法模板的角色, 这个需求里, 算法就是制作饮料的整个工序.

所以说: 模板方法定义了一个算法的步骤, 并允许子类提供其中若干个步骤的具体实现.

捋一遍整个流程

1. 我需要做一个茶:

640?wx_fmt=png

2. 然后调用茶的模板方法:

640?wx_fmt=png

3. 在模板方法里面执行下列工序:

boildWater();

brew();

pourInCup();

addCondiments();

模板方法有什么好处?

不使用模板方法时:

  • 咖啡和茶各自控制自己的算法.

  • 饮料间的代码重复.

  • 改变算法需要修改多个地方

  • 添加新饮料需要做很多工作.

  • 算法分布在了不同的类里面

使用模板方法后:

  • CaffeineBeverage这个父类控制并保护算法

  • 父类最大化的代码的复用

  • 算法只在一个地方, 改变算法也只需改变这个地方

  • 新的饮料只需实现部分工序即可

  • 父类掌握着算法, 但是依靠子类去做具体的实现.

模板方法定义

模板方法在一个方法里定义了一套算法的骨架, 算法的某些步骤可以让子类来实现. 模板方法让子类重新定义算法的某些步骤而无需改变算法的结构.

类图:

640?wx_fmt=png

这个抽象类:

640?wx_fmt=png

针对这个抽象类, 我们可以有一些扩展:

640?wx_fmt=png

看这个hook方法, 它是一个具体的方法, 但是啥也没做, 这种就叫做钩子方法. 子类可以重写该方法, 也可以不重写.

模板方法里面的钩子

所谓的钩子, 它是一个在抽象类里面声明的方法, 但是方法里面默认的实现是空的. 这也就给了子类"钩进"算法某个点的能力, 当然子类也可以不这么做, 就看子类是否需要了.

看这个带钩子的饮料父类:

640?wx_fmt=png

customerWantsCondiments()就是钩子, 子类可以重写它.

在prepareRecipe()方法里面, 通过这个钩子方法的结果来决定是否添加调料.

下面是使用这个钩子的咖啡:

640?wx_fmt=png

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();

640?wx_fmt=png

 

带钩子的父类:

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();

        }

640?wx_fmt=png

钩子和抽象方法的区别?

抽象方法是算法里面必须要实现的一个方法或步骤, 而钩子是可选实现的.

 

好莱坞设计原则

好莱坞设计原则就是: 别给我们打电话, 我们会给你打电话.

好莱坞原则可以防止依赖关系腐烂. 依赖关系腐烂是指高级别的组件依赖于低级别的组件, 它又依赖于高级别组件, 它又依赖于横向组件, 又依赖于低级别组件....以此类推. 当腐烂发生的时候, 没人会看懂你的系统是怎么设计的.

而使用好莱坞原则, 我们可以让低级别组件钩进一个系统, 但是高级别组件决定何时并且以哪种方式它们才会被需要. 换句话说就是, 高级别组件对低级别组件说: "别给我们打电话, 我们给你们打电话".

640?wx_fmt=png

好莱坞原则和模板方法模式

640?wx_fmt=png

模板方法里, 父类控制算法, 并在需要的时候调用子类的方法.

而子类从来不会直接主动调用父类的方法.

其他问题

好莱坞原则和依赖反转原则DIP的的区别?

DIP告诉我们不要使用具体的类, 尽量使用抽象类. 而好莱坞原则则是让低级别组件可以被钩进算法中去, 也没有建立低级别组件和高级别组件间的依赖关系.

三种模式比较:

模板方法模式: 子类决定如何实现算法中特定的步骤

策略模式: 封装变化的行为并使用委托来决定哪个行为被使用.

工厂方法模式: 子类决定实例化哪个具体的类.

使用模板方法做排序

看看java里面数组的排序方法:

640?wx_fmt=png

640?wx_fmt=png

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);

            }

        }

效果:

640?wx_fmt=png

其他钩子例子

java的JFrame:

640?wx_fmt=png

JFrame父类里面有一个update()方法, 它控制着算法, 我们可以使用paint()方法来钩进到该算法的那部分.

父类里面JFrame的paint()啥也没做, 就是个钩子, 我们可以在子类里面重写paint(), 上面例子的效果就是:

640?wx_fmt=png

 

另一个例子Applet小程序:

 640?wx_fmt=png

这5个方法全是重写的钩子...

我没看过winform或者wpf/sl的源码, 我估计也应该有一些钩子吧.

总结

好莱坞原则: "别给我们打电话, 我们给你打电话"

模板方法模式: 模板方法在一个方法里定义了一套算法的骨架, 算法的某些步骤可以让子类来实现. 模板方法让子类重新定义算法的某些步骤而无需改变算法的结构

该系列的源码: https://github.com/solenovex/Head-First-Design-Patterns-in-CSharp

相关文章:

原文地址 :https://www.cnblogs.com/cgzl/p/8865861.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

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

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

相关文章

P2396-yyylovesMathsVII【状压dp】

正题 题目链接:https://www.luogu.com.cn/problem/P2396 题目大意 nnn个数字&#xff0c;依次选择若干个数字使得没有任何一个前缀和等于厄运数字&#xff0c;厄运数字有mmm个。 解题思路 先预处理出disidis_idisi​表示集合iii的数字和。 然后对于disidis_idisi​不等于厄运…

9、oracle数据库下的视图和同义词

ORACLE下的视图和同义词 1.视图 视图就是一个查询的结果&#xff0c;可能包含一张表或者多张表的信息&#xff0c;创建视图其目的在于&#xff0c;便于查看表中的信息。视图只是在逻辑上存在。 1.1创建/修改视图 创建视图一定要有CREATE VIEW权限&#xff0c;基本语法为&am…

.NET Core Community 首个千星项目诞生:CAP

项目简介在我们构建 SOA 或者 微服务系统的过程中&#xff0c;我们通常需要使用事件来对各个服务进行集成&#xff0c;在这过程中简单的使用消息队列并不能保证数据的最终一致性&#xff0c; CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环…

P2463-[SDOI2008]Sandy的卡片【SA,二分答案】

正题 题目链接:https://www.luogu.com.cn/problem/P2463 题目大意 nnn个长度不同的数字序列&#xff0c;序列的子串相同的定义是该子串相邻的两两差相同。 求公共子串的最长长度。 解题思路 做一个差分后问题就变为了求nnn个串的最长公共子串。 我们将所有的字符串接在一起…

10、oracle下PL/SQL编程基础

ORACLE下的PL/SQL编程基础 PL/SQL语言是程序化程序设计语言&#xff0c;块是PL/SQL编程中的基本结构&#xff0c;其优点在于支持SQL、支持面向对象编程、性能好、可移植性、与sql集成、安全性高等。 1.基本语法 1.1 基本语法结构 [set severoutput on] declare 变量&…

.NET Core 从 Github到 Nuget 持续集成、部署

一.前言Nuget 作为一个.NET研发人员&#xff0c;我想你都不会陌生&#xff0c;他为我们提供非常方便的程序包管理&#xff0c;不管是版本&#xff0c;还是包的依赖都能轻松应对&#xff0c;可以说是我们的好助手。而 Nuget 除了官方nuget.org以外&#xff0c;我们也可以用起提供…

P2336-[SCOI2012]喵星球上的点名【SA,树状数组】

正题 题目链接:https://www.luogu.com.cn/problem/P2336 题目大意 nnn个名字(每个名字两个串)&#xff0c;mmm次点名&#xff0c;如果一次点名里是一个名字两个串中的子串该人就要答到。 对于每次点名求多少个人答到&#xff0c;每个名字求答到多少次。 解题思路 先考虑第一…

11、oracle数据库下的事务和触发器

ORACLE下的事务和触发器 1.事务 事务是数据库的一种机制&#xff0c;当执行一系列操作时&#xff0c;事务可以保证这一系列操作都能完成&#xff0c;在此期间如果出现问题&#xff0c;则这一系列操作导致的结果均回退到原始状态。这样就保证了数据的一致性&#xff0c;事务在…

微软发布自己定制的 Linux 内核和发行版,面向物联网

微软首次发布了自己的定制 Linux 内核和发行版。在旧金山举行的新闻发布会上&#xff0c;微软宣布了针对物联网设备的解决方案 Azure Sphere。Azure Sphere 包含三个组件。其中之一是微软设计的 Sphere MCU&#xff0c;将免费提供给厂商&#xff0c;联发科将在今年晚些时候推出…

P3804-[模板]后缀自动机【SAM】

正题 题目链接:https://www.luogu.com.cn/problem/P3804 题目大意 长度为nnn的串&#xff0c;求一个出现次数不小于2的子串使得子串长度乘上出现次数最大。 解题思路 构建SAMSAMSAM的时候统计一下每个子串出现多少次即可。 codecodecode #include<cstdio> #include&l…

MEDIATR 一个低调的中介者类库

微软官方的开源项目eShopOnContainers中&#xff0c;用到了一个实现中介者模式的类库&#xff1a;MediatR。这个类库的作者叫Jimmy Bogard&#xff0c;在其gtihub主页上可以看到&#xff0c;注明的对象映射组件AutoMapper 就是他写的。其博客上的自我介绍是这么写的&#xff1a…

12、oracle数据库下的存储过程和函数

ORACLE下的存储过程和函数 存储过程和函数是一种操作块&#xff0c;用来流程化、整体化处理业务逻辑的数据库操作方式。我理解的是相当于java开发语言中方法的概念&#xff0c;存储过程和函数的区别在于函数可以有返回值&#xff0c;而过程没有返回值。 1.存储过程 -- 创建存…

Codeforces Gym 101173 CERC 16 D BZOJ 4790 Dancing Disks

Codeforces Gym 101173 CERC 16 D & BZOJ 4790 Dancing Disks 强烈安利这道构造题目&#xff0c;非常有意思。 这里用到的思想是归并排序&#xff01; 多路归并排序&#xff01; 我们这样想&#xff0c;假设6*6的网格中除了最后一个网格外&#xff0c;其他的凡是有元素…

P5496-[模板]回文自动机【PAM】

正题 题目链接:https://www.luogu.com.cn/problem/P5496 题目大意 长度为nnn的字符串&#xff0c;求每个字符串作为结尾有多少个回文串。 解题思路 PAMPAMPAM。 下面是个人对PAMPAMPAM的一些理解(不是讲解)&#xff1a; 每个节点表示一个回文串&#xff0c;就是根到其的路径…

ApacheSkyWalking APM 生态衍生多语言监控, 支持 .NET Core

Apache SkyWalking .NET core 探针发布&#xff01;GitHub: https://github.com/apache/incubator-skywalking 码云Gitee: https://gitee.com/OpenSkywalking/sky-walkingApache SkyWalking在4月初&#xff0c;发布了加入Apache孵化器后的第一个版本&#xff1a;5.0.0-alpha。…

13、oracle数据库下的游标

ORACLE下的游标操作 游标是sql的一个内存工作区&#xff0c;由系统或者用户以变量的形式定义。游标的作用是用于临时存储从数据库中提取的数据块。游标有静态游标、动态游标之分&#xff0c;静态游标又可分为隐式游标和显式游标。静态游标是在编译时期就决定了结果集的&#x…

背包系列

庆功会&#xff08;ssl 2289&#xff09; Description 为了庆贺班级在校运动会上取得第一名的成绩&#xff0c;班主任决定开一场庆功会&#xff0c;为此拔款购买奖品奖励运动员&#xff0c;期望拔款金额能购买最大价值的奖品&#xff0c;可以补充他们的精力和体力。 Input …

使用TFS CI/CD 完成 VSTS 插件自动化部署和发布

Visual Studio Team Service 经过了13年的版本演进和5年的在线运营&#xff0c;现在已经是最成熟的商用DevOps工具链&#xff0c;Marketplace作为VSTS为全球开发者提供各种类型的插件市场&#xff0c;为Visual Studio, Visual Studio Code和Visual Studio Team Service本身提供…

P4248-[AHOI2013]差异【SAM or SA】

正题 题目链接:https://www.luogu.com.cn/problem/P4248 题目大意 TiT_iTi​表示后缀i∼ni\sim ni∼n 一个字符串求 ∑i1n∑jinlen(Ti)len(Tj)−2∗lcp(Ti,Tj)\sum_{i1}^n\sum_{ji}^nlen(T_i)len(T_j)-2*lcp(T_i,T_j)i1∑n​ji∑n​len(Ti​)len(Tj​)−2∗lcp(Ti​,Tj​) 解题…

14、ORACLE下的基本SQL操作

ORACLE下的基本SQL操作 1.获取表字段 select * from user_tab_columns where Table_Name用户表 order by column_name2.获取表注释 select * from user_tab_comments where Table_Name用户表 order by Table_Name3.获取字段注释 select * from user_col_comments where Table_…