设计模式:模板方法(Template method)
所以用代码来创建如下:
咖啡:Caffee.java
public class Coffee {
void prepareRecipe(){
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
} void boilWater(){
System.out.println("Boiling water...");
} public void brewCoffeeGrinds(){
System.out.println("Dripping Coffee through filter...");
} void pourInCup(){
System.out.println("Pouring into Cup...");
} public void addSugarAndMilk(){
System.out.println("Adding Sugar and Milk...");
}
}
茶:Tea.java
public class Tea {
void prepareRecipe(){
boilWater();
steepTeaBag();
pourInCup();
addLemon();
} void boilWater(){
System.out.println("Boiling water...");
} public void steepTeaBag(){
System.out.println("Steeping the tea...");
} void pourInCup(){
System.out.println("Pouring into Cup...");
} public void addLemon(){
System.out.println("Adding Lemon...");
}
}
通过上面两个类的实现我们发现一些重复的代码。从一开始接触设计模式,我们就知道这样一个设计原则:将用中需要变化的部分取出并“封装”起来,即我们需要将某些相同代码抽象出来。根据这个原则我们可以确认这个应用中存在两个不变的部分:把水煮沸、把茶倒入杯子中,需要变化的有:用沸水冲泡咖啡(茶)、加入糖和牛奶(加入柠檬)。所以这里需要将这个变化部分抽象出来,交由子类去实现。
我们再仔细看看这两个步骤还有什么相同之处呢?这里我们发现用沸水冲泡咖啡和泡茶叶存在一个共同点,那就是用沸水冲泡,只不过冲泡的对象不同罢了,加入糖和牛奶(柠檬)同样的道理,都是加入调料,只不过加入的调料不同而已。所以这里可以将prepareRecipe()方法做如下修改:
做了上面的一些修改,我们可以看出泡咖啡和泡茶将共用一个相同的泡法(算法):
把水煮沸——>用沸水冲泡——>倒入杯子中——>加入调料。
通过上面的一些简要的介绍,对模板方法模式有了一个初步的认识。那么什么是模板方法模式呢?
一、模式定义
所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。
其实所谓模板就是一个方法,这个方法将算法的实现定义成了一组步骤,其中任何步骤都是可以抽象的,交由子类来负责实现。这样就可以保证算法的结构保持不变,同时由子类提供部分实现。
模板是一个方法,那么他与普通的方法存在什么不同呢?模板方法是定义在抽象类中,把基本操作方法组合在一起形成一个总算法或者一组步骤的方法。而普通的方法是实现各个步骤的方法,我们可以认为普通方法是模板方法的一个组成部分。
二、模式结构
从上面的结构可以看出,模板方法模式就两个角色:
AbstractClass: 抽象类
ConcreteClass: 具体子类
其中抽象类提供一组算法和部分逻辑的实现,具体子类实现剩余逻辑。
三、模式实现
使用上面的泡咖啡和泡茶。
首先是抽象类,该抽象类提供了冲泡咖啡或者茶的具体流程,并且实现了逻辑步骤,煮沸水和倒入杯子中。将用沸水冲泡和加入调料交由具体的子类(咖啡、茶)来实现。
public abstract class CaffeineBeverage { /**
* @return void
* @desc 模板方法,用来控制泡茶与冲咖啡的流程
* 申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
*/
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
} /**
* @return void
* @desc 将brew()、addCondiment()声明为抽象类,具体操作由子类实现
*/
abstract void brew(); abstract void addCondiments(); void boilWater() {
System.out.println("Boiling water...");
} void pourInCup() {
System.out.println("Pouring into Cup...");
}
}
然后是具体的子类实现:
Coffee.java
public class Coffee extends CaffeineBeverage { void addCondiments() {
System.out.println("Adding Sugar and Milk...");
} void brew() {
System.out.println("Dripping Coffee through filter...");
} }
Tea.java
public class Tea extends CaffeineBeverage { void addCondiments() {
System.out.println("Adding Lemon..."); } void brew() {
System.out.println("Steeping the tea...");
} }
完成,做了这么久终于可以泡杯咖啡来喝了。
public class Test {
public static void main(String[] args) {
Tea tea = new Tea();
tea.prepareRecipe();
}
}
从上面的运行结果可以看出,我们的模板方法模式表现的非常良好,但是我们似乎忽略了一些东西?如果某些客户并不喜欢加入调料,而喜欢原生态的,但是我们的算法总是会给客户加入调料,怎么解决?
遇到这个问题我们可以使用钩子。所谓钩子就是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以使子类能够对算法的不同点进行挂钩,即让子类能够对模板方法中某些即将发生变化的步骤做出相应的反应。当然要不要挂钩,由子类决定。
所以对于上面的要求,我们可以做出如下修改。
public abstract class CaffeineBeverageWithHook {
void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { //如果顾客需要添加调料,我们才会调用addCondiments()方法
addCondiments();
}
} abstract void brew(); abstract void addCondiments(); void boilWater() {
System.out.println("Boiling water...");
} void pourInCup() {
System.out.println("Pouring into Cup...");
} public boolean customerWantsCondiments() {
return true;
}
}
客户是否需要加入调料,只需要回答y或者n
public class CoffeeWithHook extends CaffeineBeverageWithHook {
void addCondiments() {
System.out.println("Adding Sugar and Milk...");
} void brew() {
System.out.println("Dripping Coffee through filter...");
} /**
* 覆盖该钩子,提供自己的实现方法
*/
public boolean customerWantsCondiments() {
if ("y".equals(getUserInput().toLowerCase())) {
return true;
} else {
return false;
}
} public String getUserInput() {
String answer = null;
System.out.print("Would you like milk and sugar with your coffee(y/n):");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (answer == null) {
return "n";
}
return answer; }
}
测试程序
public class Test {
public static void main(String[] args) {
CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.prepareRecipe();
}
}
运行结果
从上面可以看出钩子能够作为条件来进行控制。
四、模式优缺点
优点
1、模板方法模式在定义了一组算法,将具体的实现交由子类负责。
2、模板方法模式是一种代码复用的基本技术。
3、模板方法模式导致一种反向的控制结构,通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,符合“开闭原则”。
缺点
每一个不同的实现都需要一个子类来实现,导致类的个数增加,是的系统更加庞大。
五、使用场景
1、 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2、 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
3、控制子类的扩展。
六、模式总结
1、 模板方法模式定义了算法的步骤,将这些步骤的实现延迟到了子类。
2、 模板方法模式为我们提供了一种代码复用的重要技巧。
3、 模板方法模式的抽象类可以定义抽象方法、具体方法和钩子。
4、 为了防止子类改变算法的实现步骤,我们可以将模板方法声明为final。
设计模式:模板方法(Template method)的更多相关文章
- 一天一个设计模式——模板方法(Template Method)模式
一.模式说明 现实世界中的模板是用于将事物的结构规律予以固定化.标准化的成果,它体现了结构形式的标准化.例如镂空文字印刷的模板,通过某个模板印刷出来的文字字体大小都是一模一样,但是具体使用什么材质的颜 ...
- C#设计模式——模板方法(Template Method)
一.概述在软件开发中,对某一项操作往往有固定的算法结构,而具体的子步骤会因为不同的需要而有所不同.如何可以在稳定算法结构的同时来灵活应对子步骤变化的需求呢?二.模板方法模板方法是一种常见的设计模式,它 ...
- 宋宝华:Linux设备驱动框架里的设计模式之——模板方法(Template Method)
本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前言 <设计模式>这本经典 ...
- 设计模式(22)--Template Method(模板方法模式)--行为型
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.模式定义: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声 ...
- 行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式
1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子 ...
- 模板方法(Template Method)(父类声明算法骨架,子类具体不同实现)
在阎宏博士的<JAVA与模式>一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式 ...
- 设计模式 : Template method 模板方法模式 -- 行为型
设计模式中,模板模式面向的是方法级别的流程.(不过好像世界上大部分问题,都可以抽象点.抽象点吧,最后抽象到一个方法里面吧.) 1. 一个方法,可以用来描述一个流程,这个流程涉及多个环节,不同环节可 ...
- 设计模式二--模板方法Template method
模式分类: 书籍推荐:重构-改善既有代码的设计 重构获得模式 设计模式:现代软件设计的特征是"需求的频繁变化".设计模式的要点是 "寻找变化点,然后在变化点处应用设计模式 ...
- 设计模式之---模板方法template method的使用
在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的.Template Method ...
随机推荐
- poj 2431 Expedition 贪心+优先队列 很好很好的一道题!!!
Expedition Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 10025 Accepted: 2918 Descr ...
- “美登杯”上海市高校大学生程序设计邀请赛 (华东理工大学) E 小花梨的数组 线段树
题意 分析 预处理出每个数的最小素因子,首先可以知道\(minprime(x*minprime(x))=minprime(x)\),我们用线段树维护区间最大值\(mx[p]\),注意这里的最大值并不是 ...
- k8s集群节点更换ip 或者 k8s集群添加新节点
1.需求情景:机房网络调整,突然要回收我k8s集群上一台node节点机器的ip,并调予新的ip到这台机器上,所以有了k8s集群节点更换ip一说:同时,k8s集群节点更换ip也相当于k8s集群添加新节点 ...
- thinkphp is NULL表达式写法
thinkphp 中如果这样写 $where['status']=array('EQ','NULL'),打印出来sql是WHERE ( `status` = 'NULL' ):而我想要的是 `sta ...
- JavaWeb_(SSH论坛)_一、项目入门
基于SSH框架的小型论坛项目 一.项目入门 传送门 二.框架整合 传送门 三.用户模块 传送门 四.页面显示 传送门 五.帖子模块 传送门 六.点赞模块 传送门 七.辅助模块 传送门 项目已上传至gi ...
- python-线性回归预测
导入包 # Required Packages import matplotlib.pyplot as plt import numpy as np import pandas as pd from ...
- [BZOJ4827][Hnoi2017]礼物(FFT)
4827: [Hnoi2017]礼物 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1315 Solved: 915[Submit][Status] ...
- 0.4 IDEA报错以及解决方式
0.4 IDEA报错以及解决方式一.端口被占用 [WARNING] FAILED SelectChannelConnector@0.0.0.0:8080: java.net.BindException ...
- mysql 数据库备份 -- (定时任务)
场景: 我们经常需要对数据库备份 方式一:mysql 数据备份方式 在linux 备份方式 通常采用 mysqldump -uroot -ppassword --database 数据库名 > ...
- Java多线程-线程中止
不正确的线程中止-Stop Stop:中止线程,并且清除监控器锁的信息,但是可能导致 线程安全问题,JDK不建议用. Destroy: JDK未实现该方法. /** * @author simon * ...