首先我们先来看两个例子:冲咖啡和泡茶。冲咖啡和泡茶的基本流程如下:

所以用代码来创建如下:

咖啡: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)的更多相关文章

  1. 一天一个设计模式——模板方法(Template Method)模式

    一.模式说明 现实世界中的模板是用于将事物的结构规律予以固定化.标准化的成果,它体现了结构形式的标准化.例如镂空文字印刷的模板,通过某个模板印刷出来的文字字体大小都是一模一样,但是具体使用什么材质的颜 ...

  2. C#设计模式——模板方法(Template Method)

    一.概述在软件开发中,对某一项操作往往有固定的算法结构,而具体的子步骤会因为不同的需要而有所不同.如何可以在稳定算法结构的同时来灵活应对子步骤变化的需求呢?二.模板方法模板方法是一种常见的设计模式,它 ...

  3. 宋宝华:Linux设备驱动框架里的设计模式之——模板方法(Template Method)

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前言 <设计模式>这本经典 ...

  4. 设计模式(22)--Template Method(模板方法模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声 ...

  5. 行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式

    1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子 ...

  6. 模板方法(Template Method)(父类声明算法骨架,子类具体不同实现)

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

  7. 设计模式 : Template method 模板方法模式 -- 行为型

      设计模式中,模板模式面向的是方法级别的流程.(不过好像世界上大部分问题,都可以抽象点.抽象点吧,最后抽象到一个方法里面吧.) 1. 一个方法,可以用来描述一个流程,这个流程涉及多个环节,不同环节可 ...

  8. 设计模式二--模板方法Template method

    模式分类: 书籍推荐:重构-改善既有代码的设计 重构获得模式 设计模式:现代软件设计的特征是"需求的频繁变化".设计模式的要点是 "寻找变化点,然后在变化点处应用设计模式 ...

  9. 设计模式之---模板方法template method的使用

    在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的.Template Method ...

随机推荐

  1. 第七届蓝桥杯试题c/c++A组方格填数 回溯法

    方格填数如下的10个格子   +--+--+--+   |  |  |  |+--+--+--+--+|  |  |  |  |+--+--+--+--+|  |  |  |+--+--+--+(如果 ...

  2. WordPress显示评论者IP归属地、浏览器、终端设备、电信运营商

    在网上查资料闲逛,偶然间看到了张戈博客的评论框有点意思,于是就收走拿到了我的米扑博客. 本文为米扑博客原创:总结分享 WordPress显示评论者IP归属地.浏览器.终端设备.电信运营商 WordPr ...

  3. Latex里引用多个公式,如何将公式合并?

    如果是想要的效果:(1)-(3),怎么操作?类似于用\cite引用多个文献那样吗? 1. \eqref{lable 1, lable 2, label 3}? 得到的结果:3个问号 ??? 2.\eq ...

  4. MySQL_(Java)使用JDBC向数据库发起查询请求

    MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC创建用户名和密码校验查询方法 传送门 MySQL_(Java)使用preparestatement ...

  5. HAOI2010软件安装

    首先tarjan缩点应该能看出来,然后我用topsort跑了个DAG上的一维dp,结果WA的很惨. 其实用DAG应该也能做,但是DAG强调整体顺序,而对一些局部问题,例如两个儿子怎么分配,是否给当前节 ...

  6. 统计mysql某个数据库的表数量以及表记录数

        统计MySQL中某个数据库中有多少张表 SELECT count(*) TABLES, table_schema FROM information_schema.TABLES    where ...

  7. 基于XML的AOP配置(2)-环绕通知

    配置方式: <aop:config> <aop:pointcut expression="execution(* com.itheima.service.impl.*.*( ...

  8. LeetCode 6. Z字形变换(ZigZag Conversion)

    题目描述 将字符串 "PAYPALISHIRING" 以Z字形排列成给定的行数: P A H N A P L S I I G Y I R 之后从左往右,逐行读取字符:"P ...

  9. Chrome Development Tool: [VM] file from javascript

    Chrome Development Tool: [VM] file from javascript [VM] (scriptId) has no special meaning. It's a du ...

  10. 给JAVA的eclipse IDE 在线安装 SVN插件 / 给 eclipse 添加打开所在的文件夹功能

    http://subclipse.tigris.org/servlets/ProjectProcess?pageID=p4wYuA 首先,在这个网址找着最新在线安装链接 就是那个 Links for ...