前言

这一章的模板方法模式,个人感觉它是一个简单,并且实用的设计模式,先说说它的定义:

模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。(百度百科)

额, 这段定义呢,如果说我在不了解这个设计模式的时候,我看着反正是云里雾里的,毕竟定义嘛,就是用一堆看不懂的名词把一个看不懂的名词描述出来,但是学了这个设计模式,反过来看,又会觉得它的定义很正确。

模板方法模式的关键点有3个:

1,有一个由多个步骤构成的方法(模板方法)

2,子类可以自行实现其中的一个或多个步骤(模板方法中的步骤)

3,架构允许的情况下,子类可以重新定义某些步骤

话说,这不就是上面那段话吗?列成3点以后咋感觉越看越玄了呢?难道这就是传说中的玄学编程?

列出来的目的是,后面的例子里面会依次讲到这3点,话不多说,代码在idea里面已经蓄势待发!

模板方法模式基本实现

1,故事背景

在实现之前呢,需要有一个历史背景,不然不知道来龙去脉,容易印象不深刻,headfirst里面是这样的一个例子:

在一个店里面,有2种饮料,它们的冲泡步骤是这样的:

1,咖啡:把水煮沸,用沸水冲泡咖啡,倒进杯子,加糖和牛奶

2,茶:把水煮沸,用沸水浸泡茶叶,倒进杯子,加柠檬

当然,本着没有专业的自动化酿造技术的咖啡店不是一个好的科技公司,这段逻辑当然得用代码来实现了啊,然后就进入大家最喜欢的贴代码环节:

/**
* 咖啡
*/
public class Coffee { /**
* 准备
*/
public void prepare() {
boilWater();//把水煮沸
brewCoffeeGrinds();//冲泡咖啡
pourInCup();//倒进杯子
addSugarAndMilk();//添加糖和牛奶
} /**
* 把水煮沸
*/
private void boilWater() {
System.out.println("把水煮沸");
} /**
* 冲泡咖啡
*/
private void brewCoffeeGrinds() {
System.out.println("用沸水冲泡咖啡");
} /**
* 倒进杯子
*/
private void pourInCup() {
System.out.println("倒进杯子");
} /**
* 添加糖和牛奶
*/
private void addSugarAndMilk() {
System.out.println("添加糖和牛奶");
}
} /**
* 茶
*/
public class Tea { /**
* 准备
*/
public void prepare() {
boilWater();//把水煮沸
steepTeaBag();//泡茶
pourInCup();//倒进杯子
addLemon();//加柠檬
} /**
* 把水煮沸
*/
private void boilWater() {
System.out.println("把水煮沸");
} /**
* 泡茶
*/
private void steepTeaBag() {
System.out.println("用沸水浸泡茶叶");
} /**
* 倒进杯子
*/
private void pourInCup() {
System.out.println("倒进杯子");
} /**
* 加柠檬
*/
private void addLemon() {
System.out.println("添加柠檬");
}
}

上面贴了咖啡和茶的实现,对外提供的public方法是prepare()方法,其他的内部方法,都是private(不需要的方法不要提供出去,外面的世界锅太多,它们还小,经不住那么多的打击),按道理来说,上面两段代码,思路清晰,注释完整,代码整洁。

但是,boilWater(),pourInCup()两个方法,其实内容是一模一样的,对于一个程序来说,有2段一模一样的代码的时候,就应该思考,是不是有什么地方不对。因为,有2段就表示要改2个同样的地方,有10段,就要改10个同样的地方,System.out.println()当然能改啊。

逻辑一多咋办?逻辑一多当然也能改啊,毕竟总所周知,程序员是只需要3个键(Ctrl,C,V)就能正常工作的。但是,还有键盘在别人手上啊,他们写在什么地方的知道不?

2,逻辑抽象第一版

逻辑上来说,这个地方就会被抽象出一个公共类:

/**
* 咖啡因的饮料(将烧水和倒进杯子两个方法抽象出来)
*/
public abstract class CaffeineBeverage { public abstract void prepare();//子类必须要有一个准备饮料的方法 /**
* 把水煮沸
*/
protected void boilWater() {
System.out.println("把水煮沸");
} /**
* 倒进杯子
*/
protected void pourInCup() {
System.out.println("倒进杯子");
}
}

咖啡和茶的实现就会变成下面这样:

/**
* 咖啡
*/
public class Coffee extends CaffeineBeverage{ /**
* 准备
*/
public void prepare() {
boilWater();//把水煮沸
brewCoffeeGrinds();//冲泡咖啡
pourInCup();//倒进杯子
addSugarAndMilk();//添加糖和牛奶
} /**
* 冲泡咖啡
*/
private void brewCoffeeGrinds() {
System.out.println("用沸水冲泡咖啡");
} /**
* 添加糖和牛奶
*/
private void addSugarAndMilk() {
System.out.println("添加糖和牛奶");
}
} /**
* 茶
*/
public class Tea extends CaffeineBeverage{ /**
* 准备
*/
public void prepare() {
boilWater();//把水煮沸
steepTeaBag();//泡茶
pourInCup();//倒进杯子
addLemon();//加柠檬
} /**
* 泡茶
*/
private void steepTeaBag() {
System.out.println("用沸水浸泡茶叶");
} /**
* 加柠檬
*/
private void addLemon() {
System.out.println("添加柠檬");
}
}

一般来说,实际业务中,抽象到这个地方,已经比复制粘贴的时候好很多了。但是,很多时候,不能只看表面,还需要总结更加深层次的东西,让业务的实现变得更加简单。

比如在这个例子中,prepare()方法中的步骤还可以总结,抽象。

原来的冲泡步骤:

1,咖啡:把水煮沸,用沸水冲泡咖啡,倒进杯子,加糖和牛奶

2,茶:把水煮沸,用沸水浸泡茶叶,倒进杯子,加柠檬

抽象后:

咖啡/茶:把水煮沸,用沸水 【冲泡咖啡/浸泡茶叶】,倒进杯子,加 【糖和牛奶/柠檬】

第2步和第4步,还可以抽象成,冲泡,加调味品

3,模板方法模式抽象

那么抽象类的代码就会变成这样:

/**
* 咖啡因的饮料(模板方法模式)
*/
public abstract class CaffeineBeverage { /**
* 准备(构造成final方法,防止子类重写算法)
*/
final void prepare() {
boilWater();
brew();
pourInCup();
addCondiments();
} /**
* 冲泡
*/
abstract void brew(); /**
* 添加调料
*/
abstract void addCondiments(); /**
* 把水煮沸
*/
public void boilWater() {
System.out.println("把水煮沸");
} /**
* 倒进杯子
*/
public void pourInCup() {
System.out.println("倒进杯子");
}
}

咖啡和茶的实现如下:

/**
* 咖啡
*/
public class Coffee extends CaffeineBeverage { /**
* 冲泡咖啡
*/
void brew() {
System.out.println("用沸水冲泡咖啡");
} /**
* 添加糖和牛奶
*/
void addCondiments() {
System.out.println("添加糖和牛奶");
}
} /**
* 茶
*/
public class Tea extends CaffeineBeverage { /**
* 泡茶
*/
void brew() {
System.out.println("用沸水浸泡茶叶");
} /**
* 加柠檬
*/
void addCondiments() {
System.out.println("添加柠檬");
}
}

测试类:

/**
* 测试类
*/
public class Test { public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("泡茶...");
tea.prepare();
System.out.println("冲咖啡...");
coffee.prepare();
}
}

测试结果:

泡茶...
把水煮沸
用沸水浸泡茶叶
倒进杯子
添加柠檬
冲咖啡...
把水煮沸
用沸水冲泡咖啡
倒进杯子
添加糖和牛奶

这个就是一个模板方法模式比较通用的一个实现了,中间就有模板方法模式的2个要点:

(1),有一个由多个步骤构成的方法,这里就是prepare(),由4个步骤构成的一个模板方法

(2),子类可以自行实现其中的一个或多个步骤,咖啡和茶分别都实现了brew(),addCondiments()

其实到这个地方呢,模板方法模式的一般逻辑就大概讲完了,一般来说,实现也就是上面的那个样子,但是,还是有很多时候,会出现各种各样的其他实现方式,毕竟抽象这个东西,对于不同的业务,不同的逻辑,那简直就是多种多样,只要不违背设计原则,简单易用,那么总会有意识无意识的用到模板方法模式。

接下来就介绍几种常见的操作。

模板方法模式的常见操作

1,空实现

比如说,在把水煮沸前有一个前置步骤,有的饮料需要,但是有的饮料不需要,那么就可以在模板方法里面,给它留一个位置,让子类去选择性的覆盖实现

/**
* 咖啡因的饮料(模板方法模式,空实现)
*/
public abstract class CaffeineBeverage { /**
* 准备(构造成final方法,防止子类重写算法)
*/
final void prepare() {
beforeBoilWater();
boilWater();
brew();
pourInCup();
addCondiments();
} /**
* 烧水前置操作
*/
protected void beforeBoilWater(){ }
//其他方法省略...
}

2,默认实现

比如说,加调味品这个步骤其实是可选的,加不加调味品,每种饮料加不加调味品,模板方法可以交给子类自己去控制。

/**
* 咖啡因的饮料(模板方法模式,默认实现)
*/
public abstract class CaffeineBeverage { /**
* 准备(构造成final方法,防止子类重写算法)
*/
final void prepare() {
boilWater();
brew();
pourInCup();
if(needCondiments()){
addCondiments();
}
} /**
* 是否需要调味品
*/
protected boolean needCondiments(){
return false;
}
//其他方法省略...
}

这既是模板方法模式的第3个要点,架构允许的情况下,子类可以重新定义某些步骤,只要模板方法可以定义添加或者移除某个,某些步骤,那么子类就可以根据自己的实际情况来选择实现整个方法的逻辑步骤。这个也就是这个设计模式中常说的一种叫钩子方法。

总结

模板方法模式,其实就是,在抽象类中定义一个操作中的算法的步骤,而将一些步骤的实现延迟到子类中。

这里有一个建议是,尽量在抽象的时候,保证仅存在父类的方法去调用子类的方法,而不要同时存在子类的方法去调用父类的方法。

个人觉得,这样做的原因有几点:

1,贯彻这个建议后,整个逻辑会显得很简单易懂,反正没有实现的,在子类就能找到实现

2,相互依赖调来调去的后果就是在系统越来越复杂以后,最后就没人能看懂了

3,防止抽象类的方法变动引起子类的改动,这个其实不算原因,因为一般来说,抽象类是比较稳定的,而且,子类可以调的抽象类方法在修改时肯定要考量子类的,不允许子类调用的方法肯定都处理过了,一般肯定是调不到的(诶,反射你凑过来干嘛?)

最后的最后,总结一下模板方法模式的优缺点吧

优点:

1,代码复用便于维护,子类可扩展

2,行为由父类控制,子类只需要关心自己所需要的步骤即可,开发难度低

缺点:

1,每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大

所以,如果发现子类很多,是不是要想想是不是设计模式用错了,去隔壁找找其他的设计模式吧

headfirst设计模式(9)—模板方法模式的更多相关文章

  1. HeadFirst设计模式之模板方法模式

    一. 1.The Template Method defines the steps of an algorithm and allows subclasses to provide the impl ...

  2. 乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern)

    原文:乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 模板方法模式(Template Method ...

  3. 折腾Java设计模式之模板方法模式

    博客原文地址:折腾Java设计模式之模板方法模式 模板方法模式 Define the skeleton of an algorithm in an operation, deferring some ...

  4. js设计模式——6.模板方法模式与职责链模式

    js设计模式——6.模板方法模式与职责链模式 职责链模式

  5. [C++设计模式]template 模板方法模式

    模板法模式:定义一个操作中的算法骨架.而将一些步骤延迟到子类中. 依照<headfirst 设计模式>的样例.煮茶和煮咖啡的算法框架(流程)是一样的.仅仅是有些算法的实现是不一样的,有些是 ...

  6. java设计模式之模板方法模式

    模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中. 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.通俗的说的就是有很多相同的步骤的,在某一些地方可能有一些差 ...

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

    一.引言 提到模板,大家肯定不免想到生活中的“简历模板”.“论文模板”.“Word中模版文件”等,在现实生活中,模板的概念就是——有一个规定的格式,然后每个人都可以根据自己的需求或情况去更新它,例如简 ...

  8. 【GOF23设计模式】模板方法模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_模板方法模式.钩子函数.方法回调.好莱坞原则 package com.test.templateMethod; publi ...

  9. [设计模式] 22 模板方法模式 template

    转http://www.jellythink.com/archives/407 在GOF的<设计模式:可复用面向对象软件的基础>一书中对模板方法模式是这样说的:定义一个操作中的算法骨架,而 ...

  10. java_设计模式_模板方法模式_Template Method Pattern(2016-08-11)

    定义: 定义一个操作中算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤.这里的算法的结构,可以理解为你根据需求设计出来的业务流程.特定的步骤就是指那些 ...

随机推荐

  1. Python_回调函数

    import os import stat def remove_readonly(func,path): #定义回调函数 os.chmod(path,stat.S_IWRITE) #删除文件的只读文 ...

  2. 微信小程序-获取经纬度

    微信小程序-获取经纬度 最近公司新功能 要求在外的市场人员 发送位置信息回来. 用的还是微信小程序开发.... 微信小程序 提供一个接口 getLocation 这个接口反回来的位置 相对实际位置 相 ...

  3. 【转】tomcat logs 目录下各日志文件的含义

    tomcat每次启动时,自动在logs目录下生产以下日志文件,按照日期自动备份   localhost.2016-07-05.txt   //经常用到的文件之一 ,程序异常没有被捕获的时候抛出的地方 ...

  4. Alibaba(阿里) RocketMQ入门实例

    摘自:码友18年(www.mayou18.com) what is rocketMQ? RocketMQ作为一款分布式的消息中间件(阿里的说法是不遵循任何规范的,所以不能完全用JMS的那一套东西来看它 ...

  5. Centos7 升级 gcc

    特别蛋疼的开始 最痛苦的就是一步一个坑 为了安装 vue.js,听说要安装 node.js,听说为了安装 node.js碰上了gcc版本不够的问题,此时我特别特别特别的想念盖茨大大 下载 gcc gc ...

  6. 在阿里云的CentOS环境中安装配置MySQL、JDK、Maven

    Welcome to Alibaba Cloud Elastic Compute Service ! [root@izbp19stm1x1k2io1e7r3tz ~]# rpm -Uvh http:/ ...

  7. spring MVC(十)---spring MVC整合mybatis

    spring mvc可以通过整合hibernate来实现与数据库的数据交互,也可以通过mybatis来实现,这篇文章是总结一下怎么在springmvc中整合mybatis. 首先mybatis需要用到 ...

  8. Oracle的dual表是个什么东东

    dual是一个虚拟表,用来构成select的语法规则,oracle保证dual里面永远只有一条记录.我们可以用它来做很多事情,如下: 1.查看当前用户,可以在 SQL Plus中执行下面语句 sele ...

  9. C++11标准中常用到的各种算法汇总.

    在C++11标准中定义了很多算法,这些算法可以让我们很方便的操作各种容器和数组,这里要注意一下,这些算法操作的并非容器,而是迭代器,然后通过迭代器来操作容器中的数据,算法本身并不会关注容器中保存的数据 ...

  10. BZOJ_2693_jzptab_莫比乌斯反演

    BZOJ_2693_jzptab_莫比乌斯反演 Description Input 一个正整数T表示数据组数 接下来T行 每行两个正整数 表示N.M Output T行 每行一个整数 表示第i组数据的 ...