系列文章

[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. 设计模式——模版方法模式详解(论沉迷LOL对学生的危害)

    .  实例介绍 在本例中,我们使用一个常见的场景,我们每个人都上了很多年学,中学大学硕士,有的人天生就是个天才,中学毕业就会微积分,因此得了诺贝尔数学奖:也有的人在大学里学了很多东西,过得很充实很满意 ...

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

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

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

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

  5. 【java设计模式】(10)---模版方法模式(案例解析)

    一.概念 1.概念 模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式. 它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 ...

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

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

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

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

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

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

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

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

随机推荐

  1. redis配置文件redis.conf中文版

    转账自:http://www.jb51.net/article/50605.htm # Redis示例配置文件 # 注意单位问题:当需要设置内存大小的时候,可以使用类似1k.5GB.4M这样的常见格式 ...

  2. 【转载】4412开发板嵌入式QtE应用开发环境搭建

    本文转自迅为iTOP-4412开发板实战教程书籍:http://topeetboard.com QtE应用需要使用开发工具qtcreator,本文介绍qtcreator-3.2.2的安装和使用.1. ...

  3. python3循环语句while

    Python的循环语句有for和while语句,这里讲while语句. Python中while语句的一般形式: while 条件判断 : 语句 需要注意冒号和缩进.另外,注意Python中没有do. ...

  4. SQL语句的使用

    SELECT b.level, a.cnt FROM  (SELECT `level`,COUNT(*) AS cnt FROM wlsjlog GROUP BY level) a LEFT JOIN ...

  5. 14-前端开发之CSS

    什么是 CSS ? CSS 指层叠样式表 (Cascading Style Sheets),用于对页面进行美化. 存在的方式有3种: 元素内联:在标签中使用 style='xx:xxx;' 页面嵌入: ...

  6. Python-03-基础

    一.集合 集合(set)是一个无序的.不重复的元素组合,它的主要作用如下: 去重:把一个列表变成集合,就会自动去重. 关系测试:测试两组数据之前的交集.差集.并集等关系. 常用操作 # 创建数值集合 ...

  7. haproxy 新手上路

    apache.nginx之类的反向代理(转发)功能,通常只能用于http协议,其它协议就不好使了(注:nginx据说商业版的,支持tcp协议了). haproxy可以弥补这方面的不足,haproxy支 ...

  8. ReactNative新手学习之路06滚动更新ListView数据的小示例

    本节带领大家学习使用ListView 做一个常用的滚动更新数据示例: 知识点: initialListSize={200} 第一次加载多少数据行 onEndReached={this.onEndRea ...

  9. 读取MP3专辑图片

    #define WIN32_LEAN_AND_MEAN #define NOWINRES #define NOSERVICE #define NOMCX #define NOIME #include ...

  10. 跳转Activity两种方法

    摘要:假设从A界面开启另外一个B界面根据是否需要返回数据分为两种方式 一.无需返回数据方式 在A界面中调用startActivity方法进行直接跳转即可 二.需要返回数据方式 1.在A界面中调用sta ...