[Head First设计模式]云南米线馆中的设计模式——模版方法模式
系列文章
[Head First设计模式]山西面馆中的设计模式——装饰者模式
[Head First设计模式]山西面馆中的设计模式——观察者模式
[Head First设计模式]山西面馆中的设计模式——建造者模式
[Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式
[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");
}
}
第一版设计
星巴兹咖啡和茶冲泡的分析
星巴兹咖啡和茶冲泡采用了相同的算法:
- 把水煮沸
- 用热水泡咖啡或茶
- 把饮料倒进杯子
- 在饮料内加入适当的调料
- 抽象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设计模式]云南米线馆中的设计模式——模版方法模式的更多相关文章
- NET设计模式 第二部分 行为型模式(15):模版方法模式(Template Method)
摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要. 主要内容 1.概述 2.Template Method解说 3..NET中的Te ...
- 设计模式——模版方法模式详解(论沉迷LOL对学生的危害)
. 实例介绍 在本例中,我们使用一个常见的场景,我们每个人都上了很多年学,中学大学硕士,有的人天生就是个天才,中学毕业就会微积分,因此得了诺贝尔数学奖:也有的人在大学里学了很多东西,过得很充实很满意 ...
- Head First 设计模式笔记(模版方法模式)
1.定义: 在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中.模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤. 2.类图: 3.说明: 模版方法可以理解为一个方法里面包 ...
- 【pattern】设计模式(2) - 模版方法模式
前言 一晃一年又过了,还是一样的渣. 一晃2周又过去了,还是没有坚持写博客. 本来前2天说填一下SQL注入攻击的坑,结果看下去发现还是ojdbc.jar中的代码,看不懂啊.这坑暂时填不动,强迫在元旦最 ...
- 【java设计模式】(10)---模版方法模式(案例解析)
一.概念 1.概念 模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式. 它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 ...
- 设计模式C#实现(九)——工厂方法模式和简单工厂
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method使一个类的实例化延迟到其子类. 构成: 1.Product工厂方法创建的对象的接口 2.Concrete ...
- 24种设计模式--模版方法模式【Template Method Pattern】
周三,9:00,我刚刚坐到位置,打开电脑准备开始干活.“小三,小三,叫一下其它同事,到会议室,开会”老大跑过来吼,带着淫笑.还不等大家坐稳,老大就开讲了,“告诉大家一个好消息,昨天终于把牛叉模型公司的 ...
- Java设计模式从精通到入门四 工厂方法模式
工厂方法模式 属于23中设计模式中创建型类型. 核心思想:工厂提供创建对象的接口,由子类决定实例化哪一个子类. 来源 设计模式之禅中的例子,女娲造人,通过八卦炉来进行造人,没有烧熟的为白人,烧太熟 ...
- JAVA设计模式之模版方法模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式 ...
随机推荐
- 使用 Redis 实现分布式锁
这里有一篇文章介绍了用redis实现分布式的方式 .不是简简单单的用setnx来实现,讲述了几种实际项目中的一些情况.猛击下面链接查看 http://www.oschina.net/translate ...
- Linux下磁盘挂载
公司硬盘不够用了,新买了一个存储,需要挂载到现在的系统上.前期的步骤就不说了,运维全部搞定,无非是硬件和网络那一套,这里只说分配到本人后在Linux下如何挂载. 具体步骤如下: 1.查看是否已经分配 ...
- javamail中的 javax.mail.AuthenticationFailedException: failed to connect
java.lang.RuntimeException: javax.mail.AuthenticationFailedException: failed to connect javax.mail.A ...
- jquery选择器案例
一.预期效果 实现一个效果,如下. 品牌列表默认精简显示,单击“显示全部品牌”按钮显示全部品牌,同时列表将推荐的品牌的名字高亮显示,按钮里的文字变成“精简显示品牌”.再次点击“精简显示品牌”回到初始页 ...
- hdu 4704 Sum (整数和分解+快速幂+费马小定理降幂)
题意: 给n(1<n<),求(s1+s2+s3+...+sn)mod(1e9+7).其中si表示n由i个数相加而成的种数,如n=4,则s1=1,s2=3. ...
- TJ2016 CTF Write up
No zuo no die. Use markdown to write writeup in the future......
- Libevent的IO复用技术和定时事件原理
Libevent 是一个用C语言编写的.轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大:源代码相当精炼.易 ...
- Bootstrap CSS 栅格、代码和表格
一.bootstrap栅格 Bootstrap 提供了一套响应式.移动设备优先的流式网格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列. Bootstrap 网格系统(G ...
- 洛谷P1372 又是毕业季I&&P1414 又是毕业季II[最大公约数]
P1372 又是毕业季I 题目背景 “叮铃铃铃”,随着高考最后一科结考铃声的敲响,三年青春时光顿时凝固于此刻.毕业的欣喜怎敌那离别的不舍,憧憬着未来仍毋忘逝去的歌.1000多个日夜的欢笑和泪水,全凝聚 ...
- concat() 方法用于连接两个或多个数组。
我们创建了三个数组,然后使用 concat() 把它们连接起来: <script type="text/javascript"> var arr = new Array ...