前言: 本篇我们讲解模板方法模式, 我们以咖啡和茶的冲泡来学习模板方法. 关于咖啡另一个设计模式例子也以咖啡来讲解, 可以看下: Head First 设计模式 -- 装饰者模式
废话不多说, 开始进入模板方法模式.
一, 冲泡咖啡和茶
冲泡咖啡的步骤:
(1)把水烧开
(2)用开水冲泡咖啡
(3)把咖啡到进杯子
(4)加糖和牛奶
冲泡茶的步骤
(1)把水烧开
(2)用开水浸泡茶
(3)把茶到进杯子
(4)加柠檬
实现冲泡咖啡和茶的两个类
- public class Coffee
- {
- public void PrepareRecipe()
- {
- BoilWater();
- BrewCoffeeGrinds();
- PourInCup();
- AddSugarAndMilk();
- }
- public void BoilWater()
- {
- Console.WriteLine("烧开水");
- }
- public void BrewCoffeeGrinds()
- {
- Console.WriteLine("开水冲泡咖啡");
- }
- public void PourInCup()
- {
- Console.WriteLine("咖啡到进杯子");
- }
- public void AddSugarAndMilk()
- {
- Console.WriteLine("加糖和牛奶");
- }
- }
- public class Tea
- {
- public void PrepareRecipe() {
- BoilWater();
- SteepTeaBag();
- PourInCup();
- AddLemon();
- }
- public void BoilWater()
- {
- Console.WriteLine("烧开水");
- }
- public void SteepTeaBag()
- {
- Console.WriteLine("开水浸泡茶");
- }
- public void PourInCup()
- {
- Console.WriteLine("茶到进杯子");
- }
- public void AddLemon()
- {
- Console.WriteLine("加柠檬");
- }
- }
我们从这两个类发现了一些重复的代码, 有重复的代码表示我们需要理一下我们的实现方式或者设计. 咖啡和茶的代码在第 1 步和第 3 步的方法都是一样, 其他两个方法是各自独有的, 那我们可以将相同的方法抽象到同一个基类中. 可能类似如下方式
咖啡和茶自己特有的方法, 放在自己的类中, 每个子类都覆盖 PrepareRecipe()方法, 并实现自己的冲泡.
二, 更进一步的改进
通过上面的继承改造我们感觉是否还是有些共同方法没有封装干净, 还有什么共同点没有封装了. 在咖啡和茶类中都有 PrepareRecipe()方法, 他们的步骤都是一样的, 只是特定步骤的实现方式不一样, 那我们如何抽象 PrepareRecipe()方法? 我们从每个子类中逐步抽离
1 遇到的第一个问题, 咖啡和茶的冲泡浸泡方法不一样, 所以给他们一个新的方法名称 Brew(), 然后不管是冲泡还是浸泡都用这个名称. 同样的加东西我们也类似的取一个新的方法名称来解决, 就叫 AddCondiments(). 这样一来 PrepareRecipe()方法就改造成这样:
- public void PrepareRecipe()
- {
- BoilWater();
- Brew();
- PourInCup();
- AddCondiments();
- }
2 我们现在有了新的 PrepareRecipe()方法, 需要让他符合代码, 所以我们再改造父类 CoffeineBeverage
- public abstract class CoffeineBeverage
- {
- public void PrepareRecipe()
- {
- BoilWater();
- Brew();
- PourInCup();
- AddCondiments();
- }
- public abstract void AddCondiments();
- public abstract void Brew();
- private void BoilWater()
- {
- Console.WriteLine("烧开水");
- }
- private void PourInCup()
- {
- Console.WriteLine("茶到进杯子");
- }
- }
3 最后我们需要处理下咖啡和茶类让它们继承父类, 实现自己的特有方法.
- public class Coffee: CoffeineBeverage
- {
- public override void Brew()
- {
- Console.WriteLine("开水冲泡咖啡");
- }
- public override void AddCondiments()
- {
- Console.WriteLine("加糖和牛奶");
- }
- }
- public class Tea:CoffeineBeverage
- {
- public override void Brew()
- {
- Console.WriteLine("开水浸泡茶");
- }
- public override void AddCondiments()
- {
- Console.WriteLine("加柠檬");
- }
- }
三, 模板方法模式
基本上, 通过第二步的改进我们实现的就是模板方法模式. PrepareRecipe()是我们的抽象模板方法.
(1)它是一个方法
(2)它用作一个算法的模板, 在本例中, 算法就是用来制作饮料. 在这个模板中算法内的每一个步骤都被一个方法代表. 某些方法由父类处理, 有些则由子类处理, 需要子类处理的方法在父类中被定义成抽象方法.
定义:
模板方法模式: 在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤.
类图:
四, 模板钩子
钩子是一种被声明在抽象类中的方法, 但只有空的或者默认的实现. 钩子的存在可以让子类有能力对算法的不同点进行挂钩. 要不要挂钩, 由子类决定.
例如茶里面需不需要加柠檬可以子类自己决定
- public abstract class CoffeineBeverage
- {
- public void PrepareRecipe()
- {
- BoilWater();
- Brew();
- PourInCup();
- if (CustomerWantsCondiments())
- {
- AddCondiments();
- }
- }
- public virtual bool CustomerWantsCondiments()
- {
- return true;
- }
- public abstract void AddCondiments();
- public abstract void Brew();
- private void BoilWater()
- {
- Console.WriteLine("烧开水");
- }
- private void PourInCup()
- {
- Console.WriteLine("茶到进杯子");
- }
- }
不加柠檬的茶
- public class Tea : CoffeineBeverage
- {
- public override bool CustomerWantsCondiments()
- {
- return false;
- }
- public override void Brew()
- {
- Console.WriteLine("开水浸泡茶");
- }
- public override void AddCondiments()
- {
- Console.WriteLine("加柠檬");
- }
- }
测试:
五, 好莱坞原则
模板方法模式当中涉及到好莱坞原则
好莱坞原则: 别调用 (打电话给) 我们, 我们会调用 (打电话给) 你.
好莱坞原则是一种防止 "依赖腐败" 的方法. 当高层组件依赖低层组件, 低层组件又依赖高层组件, 依赖腐败就产生了. 在这种情况下, 很难有人搞懂系统的设计和维护难度加大.
在好莱坞原则之下, 我们允许低层组件将自己挂钩到系统上, 高层组件会决定什么时候和怎样使用这些低层组件. 换句话说, 高层组件对待低层组件方式就是 "别调用我们, 我们会调用你".
而我们的模板方法模式是如何遵循这一设计原则的: CoffeineBeverage 是我们的高层组件, 它控制冲泡的算法, 只有在需要子类实现某个方法是才会调用子类. 而子类 Coffee 和 Tea 不会直接调用抽象的父类, 只是简单用来提供实现一些自身的细节.
来源: https://www.cnblogs.com/SunSpring/p/11946643.html