系列文章

[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. Fetch:下一代 Ajax 技术

    Ajax,2005年诞生的技术,至今已持续了 10 年.它是一种在客户端创建一个异步请求的技术,本质上它不算创新,是一组技术的组合.它的核心对象是 XMLHttpRequest. 简单回顾下历史 19 ...

  2. PL/SQL入门理解(一)

    1.PL/SQL概述 1)概念 pl/sql(procedural language)是Oracle在标准sql语言的基础上的扩展,可以实现定义变量.使用逻辑控制语句等 作用:默认oracle一次只能 ...

  3. spark standalone ha spark submit

    when you build a spark standalone ha cluster, when you submit your app,  you should send it to the l ...

  4. POJ1190生日蛋糕[DFS 剪枝]

    生日蛋糕 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 18236   Accepted: 6497 Description ...

  5. ASP.NET CORE dotnet run 命令使用debug方式运行

    由于我的开发环境比较复杂,每次调试一套项目都要启动好几个VS,比较繁琐,今天决定换一种方式调试,对于不该改动的代码的附加项目直接使用dotnet run命令以debug的运行方式运行, 一开始无法运行 ...

  6. 嵌入式Linux驱动学习之路(二十三)NAND FLASH驱动程序

    NAND FLASH是一个存储芯片. 在芯片上的DATA0-DATA7上既能传输数据也能传输地址. 当ALE为高电平时传输的是地址. 当CLE为高电平时传输的是命令. 当ALE和CLE都为低电平时传输 ...

  7. C语言基础(一)

    7744问题(输出所有形如aabb的4位完全平方数) 方法1: #include<stdio.h> #include<math.h> int main (){ ;a<=; ...

  8. 有return的情况下try catch finally的执行顺序(转)

    结论:1.不管有木有出现异常,finally块中代码都会执行:2.当try和catch中有return时,finally仍然会执行:3.finally是在return后面的表达式运算后执行的(此时并没 ...

  9. HTML页面和JSP页面禁止缓存

    一.JSP页面禁止缓存: 防止浏览器缓存当前访问的JSP动态页面,可以采用如下的方式进行设置,此效果如下的“HTML禁止缓存”: % 将过期日期设置为一个过去时间response.setHeader( ...

  10. C#基础系列——再也不用担心面试官问我“事件”了

    前言:作为.Net攻城狮,你面试过程中是否遇到过这样的问题呢:什么是事件?事件和委托的区别?既然事件作为一种特殊的委托,那么它的优势如何体现?诸如此类...你是否也曾经被问到过?你又是否都答出来了呢? ...