系列文章

[Head First设计模式]山西面馆中的设计模式——装饰者模式

[Head First设计模式]山西面馆中的设计模式——观察者模式

[Head First设计模式]山西面馆中的设计模式——建造者模式

[Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

[Head First设计模式]一个人的平安夜——单例模式

[Head First设计模式]抢票中的设计模式——代理模式

[Head First设计模式]面向对象的3特征5原则

[Head First设计模式]鸭子模型——策略模式

引言

第一天上班,没什么任务,就学习了下模版方法模式,这里也是现学现卖,模版方法给我的感觉是似曾相识,总感觉用过,而当时并不知道是模版方法,挺悲催的。年后第一天,吃饭是个大问题,好不容易找到一个米线馆,人非常的多,只能边等边思考模版方法模式了,跟以前一样,对于吃货来说,只有将知识和吃联系在一起,才能记得更牢。

模版方法模式是最为常见的几个模式之一,模版方法模式需要开发抽象类和具体子类的设计师之间的写作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师负责给出这个算法的各个逻辑步骤。

继承常常作为功能复用的主要工具,这时继承有被滥用的危险。所以,我们有一个设计原则:多用组合,少用继承

继承

是不是继承就根本不应该使用呢?事实上对数据的抽象、继承、封装和多态是面向对象语言的最重要特性。继承不应当被滥用,并不意味着继承根本就不该使用。在GoF书中,绝大多数模式是将依赖于继承的实现转换为基于对象的组合和聚合来实现的。模版方法模式是很少用继承来实现的模式中的一个!而且模版方法模式:鼓励恰当的使用继承。此模式可以用来改写一些拥有相同功能的相关类,将可复用的一般性的行为代码移到基类里面,而把特殊化的行为代码移到子类里面。熟悉模版方法模式便成为一个重新学习继承的好地方。

书中的例子

咖啡因饮料

咖啡类

 public class Coffee
{
public void PrepareRecipe()
{
BoilWater();
BrewCoffeeGrinds();
PourInCup();
AddSugarAndMilk();
}
//每个方法都实现算法中的一个步骤:煮沸水,冲泡咖啡,把咖啡倒进杯子,加糖和牛奶
public void BoilWater()
{ Console.WriteLine("Boiling water");
}
public void BrewCoffeeGrinds()
{
Console.WriteLine("Dripping Coffee through filter");
}
public void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
public void AddSugarAndMilk()
{ Console.WriteLine("Adding Sugar and Milk");
}
}

茶类

  public class Tea
{
public void PrepareRecipe()
{
BoilWater();
//第二步和第四步与咖啡的实现不同,其他的都一样
SteepTeaBag();
PourInCup();
AddLemon();
}
//每个方法都实现算法中的一个步骤:煮沸水,冲泡咖啡,把咖啡倒进杯子,加糖和牛奶
public void BoilWater()
{ Console.WriteLine("Boiling water");
}
//泡茶专用
public void SteepTeaBag()
{
Console.WriteLine("Steeping the tea");
}
public void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
//泡茶专用
public void AddLemon()
{ Console.WriteLine("Adding Lemon");
}
}

第一版设计

星巴兹咖啡和茶冲泡的分析

星巴兹咖啡和茶冲泡采用了相同的算法:

  1. 把水煮沸
  2. 用热水泡咖啡或茶
  3. 把饮料倒进杯子
  4. 在饮料内加入适当的调料
  • 抽象PrepareRecipe()

1、我们遇到的问题是:茶使用SteepTeaBag()和AddLemon()方法,而咖啡使用BrewCoffeeGrinds()和AddSugarAndMilk()方法。

2、无论是咖啡的冲泡,还是茶的浸泡,都是用沸水泡,我们给它一个新的方法名称,比如说Brew()。同样,无论是咖啡加糖和牛奶,还是茶加柠檬,都是加调料,我们也给它一个新的方法名称AddCondiments()。这样,新的prepareRecipe()方法看起来就象这样:

  public void prepareRecipe()
{
BoilWater();
Brew();
PourInCup();
AddCondiments();
}

3、CaffeineBeverage(咖啡因饮料)超类:

 public abstract class CaffeineBeverage
{
public void PrepareRecipe()
{
BoilWater();
//步骤2和步骤4被泛化为Brew()和AddCondiments()。
Brew();
PourInCup();
AddCondiments();
}
//这两个方法声明为抽象的,是因为咖啡和茶的做法不同,需要子类实现。
public abstract void Brew();
public abstract void AddCondiments();
public void BoilWater()
{
Console.WriteLine("Boiling water");
}
public void PourInCup()
{
Console.WriteLine("Pouring into cup");
} }

4、咖啡和茶都依赖于超类(咖啡因饮料)处理冲泡法。

  public class Coffee : CaffeineBeverage
{
public override void Brew()
{
Console.WriteLine("Dripping Coffee through filter");
} public override void AddCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
}
 public class Tea : CaffeineBeverage
{
public override void Brew()
{
Console.WriteLine("Steeping the tea");
} public override void AddCondiments()
{
Console.WriteLine("Adding Lemon");
}
}

我们做了什么?

模版方法分析

刚刚实现的就是模板方法模式。模板方法定义了一个算法步骤,并允许子类为一个或多个步骤提供实现。我们再看看咖啡因饮料类的结构(下页)。

模板方法如何工作(以泡茶为例)

模版方法模式的定义

定义一个操作中算法的骨架,而将这些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

模板方法模式类图

钩子(Hook)

钩子是一种声明为抽象类的方法,但只有空的或默认的实现。有了钩子,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

 public abstract class AbstractClass
{
//细节或略......
void Hook(){}//这是一个抽象类中不做任何事情的具体方法,即钩子
}

 对模版方法挂钩

  public abstract  class CaffeineBeverageWithHook
{
public void PrepareRecipe()
{
BoilWater();
//步骤2和步骤4被泛化为Brew()和AddCondiments()。
Brew();
PourInCup();
//有了钩子,能决定要不要覆盖方法,如果不提供自己的方法,抽象类会提供一个默认的实现
if (CustomerWantsCondiments())
{
AddCondiments();
} }
//这两个方法声明为抽象的,是因为咖啡和茶的做法不同,需要子类实现。
public abstract void Brew();
public abstract void AddCondiments();
public void BoilWater()
{
Console.WriteLine("Boiling water");
}
public void PourInCup()
{
Console.WriteLine("Pouring into cup");
}
/// <summary>
/// 这是一个钩子,子类可以覆盖这个方法,但不一定这么做
/// </summary>
/// <returns></returns>
public virtual bool CustomerWantsCondiments(){
return true;
}
}

使用钩子
为了使用钩子,我们在子类中覆盖它。在这里,钩子控制咖啡因饮料是否执行某部分算法。或更确切的说是在饮料中要不要加进调料。

 public class CoffeeWithHook : CaffeineBeverageWithHook
{
public override void Brew()
{
Console.WriteLine("Dripping Coffee through filter");
} public override void AddCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
public override bool CustomerWantsCondiments()
{
string answer = GetUserInput();
//覆盖了钩子。让用户输入对调料的决定。
if (answer.ToLower() == "y")
{
return true;
}
else
{
return false;
}
}
private string GetUserInput()
{
string answer = null;
Console.WriteLine("Would you like milk and sugar with your coffee (y/n)? ");
answer = Console.ReadLine();
return answer;
}
}

测试

  class Program
{
static void Main(string[] args)
{
CoffeeWithHook coffeeHook = new CoffeeWithHook();
Console.WriteLine("Making coffee......");
coffeeHook.PrepareRecipe();
Console.ReadLine();
}
}

结果

好莱坞原则

好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。

好莱坞原则可以防止“依赖腐败”。当高层组件依赖底层组件,底层组件又依赖高层组件,高层组件又依赖边侧组件,边侧组件又依赖高层组件......,依赖腐败就发生了。在这种情况下,没有人可以轻易搞懂系统是如何设计的。

在好莱坞原则下,允许底层组件挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件。即高层组件对底层组件的方式是:“别调用我们,我们会调用你”。

好莱坞原则与模版方法

米线馆的例子

云南米线分为:秀才米线,举人米线,状元米线等。但是他们的制作过程基本相同,只是配料不同罢了,同样可以将制作过程放在模版方法中。

 /// <summary>
/// 米线类
/// </summary>
public abstract class RiceNoodle
{
public void MakeRiceNoodle()
{ Boil();
AddRiceNoodle();
AddGreensAndMeat();
}
private void Boil() { Console.WriteLine("煮水......");
}
private void AddRiceNoodle()
{
Console.WriteLine("将米线加入砂锅中煮.....");
}
/// <summary>
/// 加配菜 一荤一素或者两荤两素等
/// </summary>
public abstract void AddGreensAndMeat();
}
  public class XiucaiNoodle:RiceNoodle
{
public override void AddGreensAndMeat()
{
Console.WriteLine("一荤一素");
}
}
  class Program
{
static void Main(string[] args)
{
Console.WriteLine("秀才米线......");
XiucaiNoodle xiucai = new XiucaiNoodle();
xiucai.MakeRiceNoodle();
Console.ReadLine();
}
}

结果

总结

模版方法理解起来还是比较轻松的,在项目中真的用到过,只是当时不知道它还有个漂亮的名字。

参考书:

Head First 设计模式

[Head First设计模式]云南米线馆中的设计模式——模版方法模式的更多相关文章

  1. NET设计模式 第二部分 行为型模式(15):模版方法模式(Template Method)

    摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要. 主要内容 1.概述 2.Template Method解说 3..NET中的Te ...

  2. Head First 设计模式笔记(模版方法模式)

    1.定义: 在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中.模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤. 2.类图:  3.说明: 模版方法可以理解为一个方法里面包 ...

  3. 【pattern】设计模式(2) - 模版方法模式

    前言 一晃一年又过了,还是一样的渣. 一晃2周又过去了,还是没有坚持写博客. 本来前2天说填一下SQL注入攻击的坑,结果看下去发现还是ojdbc.jar中的代码,看不懂啊.这坑暂时填不动,强迫在元旦最 ...

  4. 设计模式C#实现(九)——工厂方法模式和简单工厂

    工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method使一个类的实例化延迟到其子类. 构成: 1.Product工厂方法创建的对象的接口 2.Concrete ...

  5. 24种设计模式--模版方法模式【Template Method Pattern】

    周三,9:00,我刚刚坐到位置,打开电脑准备开始干活.“小三,小三,叫一下其它同事,到会议室,开会”老大跑过来吼,带着淫笑.还不等大家坐稳,老大就开讲了,“告诉大家一个好消息,昨天终于把牛叉模型公司的 ...

  6. Java设计模式从精通到入门四 工厂方法模式

    工厂方法模式 属于23中设计模式中创建型类型. 核心思想:工厂提供创建对象的接口,由子类决定实例化哪一个子类. 来源 ​ 设计模式之禅中的例子,女娲造人,通过八卦炉来进行造人,没有烧熟的为白人,烧太熟 ...

  7. JAVA设计模式之模版方法模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式 ...

  8. java设计模式之模版方法模式以及在java中作用

    模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有 ...

  9. JS常用的设计模式(10)——模版方法模式

    模式方法是预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现.听起来有点像工厂模式( 非前面说过的简单工厂模式 ). 最大的区别是,工厂模式的意图是根据子类的实现最 ...

随机推荐

  1. js unix时间戳转换

    一.unix时间戳转普通时间: var unixtime=1358932051; var unixTimestamp = new Date(unixtime* 1000); commonTime = ...

  2. 导出文本pdf文件

    出口手续往往是一些数据需求,学习文本导出到今天pdf文件.主要用于QPrinter,QPainter TextEditToPdf::TextEditToPdf(QWidget *parent, Qt: ...

  3. Redux进阶(一)

    State的不可变化带来的麻烦 在用Redux处理深度复杂的数据时会有一些麻烦.由于js的特性,我们知道当对一个对象进行复制时实际上是复制它的引用,除非你对这个对象进行深度复制.Redux要求你每次你 ...

  4. Oracle错误——tablespace &#39;XXXX&#39; does not exist

    错误 在使用IMP命令导入Oracle数据的时候,因为导出数据的数据库表空间和导入数据的数据库表空间不同,导致导入数据失败,出现:tablespace 'XXXX' does not exist 在网 ...

  5. Oarcle 入门之where关键字

    --where 关键字 --作用:过滤行 *将需要的行用where引导出来 用法: 1.判断符号:=,!=,<>,<,>,<=,>=,any,some,all; 例 ...

  6. 2.pandas数据清洗

    pandas是用于数据清洗的库,安装配置pandas需要配置许多依赖的库,而且安装十分麻烦. 解决方法:可以用Anaconda为开发环境,Anaconda内置了许多有关数据清洗和算法的库. 1.安装p ...

  7. How To create extension in Hybris(创建Hybris的扩展)

    How To create extension in Hybris What is an extension? An extension is an encapsulated piece of the ...

  8. leetcode之 两数之和

    # -*- coding: utf-8 -*- # @Time : 2018/9/27 21:41 # @Author : cxa # @File : twonum.py # @Software: P ...

  9. 【LeetCode 67_字符串_算术运算】Add Binary

    string addBinary(string a, string b) { int alen = a.size(); int blen = b.size(); ) return b; ) retur ...

  10. C语言预处理命令之条件编译(#ifdef,#else,#endif,#if等)

    转自:http://www.kuqin.com/language/20090806/66164.html 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器.可见预处理过程先于编译器 ...