文档章节

HeadFirst设计模式(九) - 模板方法模式

XuePeng77
 XuePeng77
发布于 2016/08/04 22:56
字数 1385
阅读 23
收藏 1

直接举个例子

    来看看某咖啡厅里,茶和咖啡的冲泡方式。具体如下:

    咖啡的冲泡法:

  1. 把水煮沸;
  2. 用沸水冲泡咖啡;
  3. 把咖啡倒进杯子;
  4. 加糖和牛奶;

    茶的冲泡法:

  1. 把水煮沸;
  2. 用沸水侵泡茶叶;
  3. 把茶倒进杯子;
  4. 加柠檬;

用代码实现上面的例子

public class Coffee {

	public Coffee() {
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void brewCoffeeGrinds() {
		System.out.println("用沸水冲泡咖啡...");
	}

	public void pourInCup() {
		System.out.println("把咖啡倒进杯子...");
	}

	public void addSugarAndMilk() {
		System.out.println("加糖和牛奶...");
	}
}
public class Tea {

	public Tea() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void steepTeaBag() {
		System.out.println("用沸水侵泡茶叶...");
	}

	public void pourInCup() {
		System.out.println("把茶倒进杯子...");
	}

	public void addLemon() {
		System.out.println("加柠檬...");
	}
}

    上面两个类分别是咖啡类与茶类,我们可以看到,两个类中有重复的代码,这表示我们需要清理一下设计了。在这里,既然茶和咖啡师如此地相似,似乎我们应该将共同的部分抽取出来,放进一个基类中。

抽取相同的部分

    由于咖啡与茶都含有咖啡因,所以我们抽象出一个咖啡因对象作为抽象基类,在制作咖啡与茶的过程中,boilWater()方法和pourlnCup()方法是相同的,所以在基类中实现,其余的不同函数则在子类中实现。

    在基类中,定义了一个抽象方法prepareRecipe(),它负责调用具体的制作步骤,由子类自己实现。

    在上面这个设计中,还可以更进一步,在观察一下,咖啡与茶还有什么共同点呢?注意,两种饮品都采用了相同的冲泡方法:

  1. 把水煮沸;
  2. 用热水冲泡咖啡或者茶;
  3. 把饮品倒入杯子;
  4. 添加适当的调料;

    那么,我们也将prepareRecipe()方法进行抽象,现在就来看看怎么做……

    将冲泡定义一个抽象方法brew(),在将添加调料定义一个抽象方法addCondiments(),最后将抽象方法prepareRecipe()进行修改,让他负责冲泡步骤,新的CaffeineBeverage类看起来就像这样:

public abstract class CaffeineBeverage {
	
	// 相同的步骤在基类中做  
	public void boilWater() {
		System.out.println("把水煮沸...");
	}
	
	public void pourInCup() {
		System.out.println("把饮品倒进杯子...");
	}
	
	// 不同的步骤由子类自己实现
	public abstract void brew();
	public abstract void addCondiments();
	
	// 基类负责对每一步进行调用
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}
}
package cn.net.bysoft.template;

public class Coffee extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水冲泡咖啡...");
	}
	@Override
	public void addCondiments() {
		System.out.println("加糖和牛奶...");
	}
}

package cn.net.bysoft.template;

public class Tea extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水侵泡茶叶...");
	}

	@Override
	public void addCondiments() {
		System.out.println("加柠檬...");
	}
}

    在上面的代码中,我们所做的第一件事就是把原来brewCoffeeGrinds()方法和steepTeaBag()方法进行了抽象,接着把addSugarAndMilk()方法和addLemon()方法也进行了抽象。接下来将冲泡的步骤调用封装到了基类中的prepareRecipe()方法里,最后重写了Coffee对象和Tea对象。下面进行测试:

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 冲一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
	}
}

    测试成功,现在冲泡的步骤调用由CaffeineBeverage类主导,它拥有算法,而且保护这个算法。对子类来说,CaffeineBeverage类的存在可以将代码的复用最大化。算法只存在于一个地方,所以容易修改。相同的算法有基类实现,不同的算法由子类提供完整的实现。

定义模板方法模式

    这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。

使用钩子

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

    根据上面的业务来举例,在是否添加调料时对客户进行询问,如果客户需要在添加调料。

    在上图中,基类加入了是否添加调料的钩子方法,在添加调料之间调用,算法还是由prepareRecipe()方法管理,Coffee类重写了钩子函数,通过命令行向用户确认是否要添加调料,回复y/n。而茶类没有重写钩子函数,则调用缺省函数,调用ture,每次都添加调料。下面进行测试:

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 冲一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
		
		System.out.println("\n");
		
		// 再冲一杯咖啡
		Coffee coffee2 = new Coffee();
		coffee2.prepareRecipe();
	}
}

好莱坞原则

别调用我们,我们会调用你。

    好莱坞原则可以给我们一种防止“依赖腐败” 的方法。在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎么使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”。

© 著作权归作者所有

共有 人打赏支持
XuePeng77
粉丝 41
博文 135
码字总数 178151
作品 0
丰台
(目录)设计模式(可复用面向对象软件的基础)

本系列“设计模式”博客使用Golang语言实现算法。所谓算法是指解决一个问题的步骤,个人觉得不在于语言。小弟只是最近学习Golang,所以顺带熟练一下语法知识,别无它意。 本系列博客主要介绍...

chapin
2015/01/13
0
0
[设计模式]简单工厂模式

简介 简单工厂模式 (Simple Factory) 又叫静态工厂方法(Static Factory Method)模式。 简单工厂模式通常是定义一个工厂类,这个类可以根据不同变量返回不同类的产品实例。 简单工厂模式是一...

静默虚空
2015/06/03
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
06/24
0
0
设计模式15——Template Method设计模式

Template Method模板方法设计模式定义一个操作中算法的骨架,将具体步骤的执行延迟到子类中实现。Java中的抽象类就是使用了模板方法设计模式。模板方法设计模式结构如下: 以文档处理为例,T...

小米米儿小
2014/01/24
0
0
JavaScript 中常见设计模式整理

开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。 JavaScript 中...

牧云云
05/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

linux使用ntfs-3g操作ntfs格式硬盘

Linux内核目前只支持对微软NTFS文件系统的读取。 NTFS-3G 是微软 NTFS 文件系统的一个开源实现,同时支持读和写。NTFS-3G 开发者使用 FUSE 文件系统来辅助开发,同时对可移植性有益。 安装 ...

linuxprobe16
43分钟前
1
0
kubeadm部署kubernetes集群

一、环境要求 这里使用RHEL7.5 master、etcd:192.168.10.101,主机名:master node1:192.168.10.103,主机名:node1 node2:192.168.10.104,主机名:node2 所有机子能基于主机名通信,编辑...

人在艹木中
今天
7
0
Shell特殊符号总结以及cut,sort,wc,uniq,tee,tr,split命令

特殊符号总结一 * 任意个任意字符 ? 任意一个字符 # 注释字符 \ 脱义字符 | 管道符 # #号后的备注被忽略[root@centos01 ~]# ls a.txt # 备注 a.txt[root@centos01 ~]# a=1[root@centos01...

野雪球
今天
2
0
OSChina 周二乱弹 —— 程序员圣衣

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @达尔文:分享Skeeter Davis的单曲《The End of the World》 《The End of the World》- Skeeter Davis 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
16
0
[ python import module ] 导入模块

import moudle_name ----> import module_name.py ---> import module_name.py文件路径 -----> sys.path (这里进行查找文件) # from app.web import Personimport app.web.Person as Pe......

_______-
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部