1 定义:

模板方法模式(Template Method)

Define the skeleton of an algorithm in anoperation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使用子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)

模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式

模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。

1.1 通用类图:

由图2可知,模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,模板方法模式包含如下两个角色:

       (1)抽象类(AbstractClass)在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。

抽象类中的方法分为2种:

               模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。

注意:为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。

基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。

               抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。

具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。

       (2)具体子类(ConcreteClass)它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。

1.2 通用代码:

源码实现如下:

public abstract class AbstractClass {

//基本方法

protected abstract void doSomething();

//模板方法

public final void templateMethod(){

doSomething();

}

}

public class ConcreteClass extends AbstractClass{

@Override

protected void doSomething() {

}

}

public class Client {

public static void main(String[] args){

AbstractClass c = new ConcreteClass();

c.templateMethod();

}

}

2 优点

2.1 封装不变部分,扩展可变部分(可扩展性)

把认为不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展;

2.2 提取公共部分代码,便于维护(可维护性)

不要让维护人员到处查找相似的代码;

2.3 行为由父类控制,子类实现

基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

3 缺点

按设计习惯,抽象类负责声明最抽象、最一般的事物属性与方法,实现类完成具体事物属性和方法。但模板方法却反了:抽象类定义了部分抽象方法,由子类来实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,在复杂项目中,会带来代码阅读的困难。对新手产生不适。

4 应用场景

4.1 多个子类有公有的方法,并且逻辑基本相同时。

4.2 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。

4.3 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数来约束其行为。

5 注意事项

5.1 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。

5.2 此也是一种较好的父类调用子类方法的方式。

6 扩展

6.1模板方法模式的本质

模板方法模式的本质固定算法骨架

模板方法模式主要是通过制定模板,把算法步骤固定下来,至于谁来实现,模板可以自己实现,也可以由子类去实现,还可以通过回调机制让其它类来实现。

通过固定算法骨架来约束子类的行为,并在特定的扩展点来让子类进行功能扩展,从而让程序既有很好的复用性,又有较好的扩展性。

2、对设计原则的体现

模板方法很好的体现了开闭原则和里氏替换原则。

首先从设计上分离变与不变,然后把不变的部分抽取出来,定义到父类中,比如算法骨架,一些公共的、固定的实现等。这些不变的部分被封闭起来,尽量不去修改它们,要想扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放。

其次,能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能,一个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板,并能在使用模板的地方,根据需要切换不同的具体实现。

7 范例

7.1泡茶与泡咖啡

我们可以看出泡茶和泡咖啡可以共用一个相同的泡法(算法):

把水煮沸——>用沸水冲泡——>倒入杯子中——>加入调料。

首先是抽象类,该抽象类提供了冲泡咖啡或者茶的具体流程,并且实现了逻辑步骤,煮沸水和倒入杯子中。将用沸水冲泡和加入调料交由具体的子类(咖啡、茶)来实现。

[java]  view plain copy print ?
  1. public abstract class CaffeineBeverage {
  2. /**
  3. *
  4. * @desc
  5. *          模板方法,用来控制泡茶与冲咖啡的流程
  6. *          申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
  7. * @return void
  8. */
  9. final void prepareRecipe(){
  10. boilWater();
  11. brew();
  12. pourInCup();
  13. addCondiments();
  14. }
  15. /**
  16. * @desc
  17. *       将brew()、addCondiment()声明为抽象类,具体操作由子类实现
  18. * @return void
  19. */
  20. abstract void brew();
  21. abstract void addCondiments();
  22. void boilWater(){
  23. System.out.println("Boiling water...");
  24. }
  25. void pourInCup(){
  26. System.out.println("Pouring into Cup...");
  27. }
  28. }

然后是具体的子类实现:

Coffee.java

[java]  view plain copy print ?
  1. public class Coffee extends CaffeineBeverage{
  2. void addCondiments() {
  3. System.out.println("Adding Sugar and Milk...");
  4. }
  5. void brew() {
  6. System.out.println("Dripping Coffee through filter...");
  7. }
  8. }

Tea.java

[java]  view plain copy print ?
  1. public class Tea extends CaffeineBeverage{
  2. void addCondiments() {
  3. System.out.println("Adding Lemon...");
  4. }
  5. void brew() {
  6. System.out.println("Steeping the tea...");
  7. }
  8. }

完成,做了这么久终于可以泡杯咖啡来喝了。

[java]  view plain copy print ?
  1. public class Test {
  2. public static void main(String[] args) {
  3. Tea tea = new Tea();
  4. tea.prepareRecipe();
  5. }
  6. }

从上面的运行结果可以看出,我们的模板方法模式表现的非常良好,但是我们似乎忽略了一些东西?如果某些客户并不喜欢加入调料,而喜欢原生态的,但是我们的算法总是会给客户加入调料,怎么解决?

遇到这个问题我们可以使用钩子。所谓钩子就是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以使子类能够对算法的不同点进行挂钩,即让子类能够对模板方法中某些即将发生变化的步骤做出相应的反应。当然要不要挂钩,由子类决定

所以对于上面的要求,我们可以做出如下修改。

[java]  view plain copy print ?
  1. public abstract class CaffeineBeverageWithHook {
  2. void prepareRecipe(){
  3. boilWater();
  4. brew();
  5. pourInCup();
  6. if(customerWantsCondiments()){    //如果顾客需要添加调料,我们才会调用addCondiments()方法
  7. addCondiments();
  8. }
  9. }
  10. abstract void brew();
  11. abstract void addCondiments();
  12. void boilWater(){
  13. System.out.println("Boiling water...");
  14. }
  15. void pourInCup(){
  16. System.out.println("Pouring into Cup...");
  17. }
  18. public boolean customerWantsCondiments(){
  19. return true;
  20. }
  21. }

客户是否需要加入调料,只需要回答y或者n

[java]  view plain copy print ?
  1. public class CoffeeWithHook extends CaffeineBeverageWithHook{
  2. void addCondiments() {
  3. System.out.println("Adding Sugar and Milk...");
  4. }
  5. void brew() {
  6. System.out.println("Dripping Coffee through filter...");
  7. }
  8. /**
  9. * 覆盖该钩子,提供自己的实现方法
  10. */
  11. public boolean customerWantsCondiments(){
  12. if("y".equals(getUserInput().toLowerCase())){
  13. return true;
  14. }
  15. else{
  16. return false;
  17. }
  18. }
  19. public String getUserInput(){
  20. String answer = null;
  21. System.out.print("Would you like milk and sugar with your coffee(y/n):");
  22. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  23. try {
  24. answer = in.readLine();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. if(answer == null){
  29. return "n";
  30. }
  31. return answer;
  32. }
  33. }

测试程序

[java]  view plain copy print ?
  1. public class Test {
  2. public static void main(String[] args) {
  3. CoffeeWithHook coffeeHook = new CoffeeWithHook();
  4. coffeeHook.prepareRecipe();
  5. }
  6. }

运行结果

从上面可以看出钩子能够作为条件来进行控制。

7.2(手机开机过程,有钩子实现)

实现如下:

[java]  view plain copy
  1. package _04_TemplateMethod;
  2. public abstract class Phone {
  3. //模板方法:开机
  4. public final void turnOn(){
  5. initSystem();
  6. showLogo();
  7. if(isPlayMusic())
  8. playMusic();
  9. lockAndSleep();
  10. }
  11. //初始系统
  12. protected abstract void initSystem();
  13. //显示开机logo
  14. protected abstract void showLogo();
  15. //播放开机音乐
  16. protected abstract void playMusic();
  17. //锁屏并待机
  18. protected abstract void lockAndSleep();
  19. //是否播放开机音乐  :钩子函数
  20. protected boolean isPlayMusic(){
  21. return true;
  22. }
  23. }
  24. public class SmartPhone extends Phone{
  25. @Override
  26. protected void initSystem() {
  27. System.out.println("正在加载智能机系统 。。。");
  28. }
  29. @Override
  30. protected void showLogo() {
  31. System.out.println("显示LOGO:3G梦想!");
  32. }
  33. @Override
  34. protected void playMusic() {
  35. System.out.println("播放开机音乐:123456");
  36. }
  37. @Override
  38. protected void lockAndSleep() {
  39. System.out.println("一切就绪,锁屏待机");
  40. }
  41. @Override
  42. protected boolean isPlayMusic(){
  43. return false;//什么年代了,智能机不播开机音乐!
  44. }
  45. }
  46. public class FeaturePhone extends Phone{
  47. @Override
  48. protected void initSystem() {
  49. System.out.println("正在加载功能机系统 。。。");
  50. }
  51. @Override
  52. protected void showLogo() {
  53. System.out.println("显示LOGO:NOKIA待机王!");
  54. }
  55. @Override
  56. protected void playMusic() {
  57. System.out.println("播放开机音乐:123456");
  58. }
  59. @Override
  60. protected void lockAndSleep() {
  61. System.out.println("一切就绪,锁屏待机");
  62. }
  63. }
  64. public class TestPhone {
  65. public static void main(String[] args) {
  66. Phone sp = new SmartPhone();
  67. sp.turnOn();
  68. Phone fp = new FeaturePhone();
  69. fp.turnOn();
  70. }
  71. }
  72. 结果:
  73. 正在加载智能机系统 。。。
  74. 显示LOGO:3G梦想!
  75. 一切就绪,锁屏待机
  76. 正在加载功能机系统 。。。
  77. 显示LOGO:NOKIA待机王!
  78. 播放开机音乐:123456
  79. 一切就绪,锁屏待机

JAVA设计模式(14) —行为型模板方法模式(Template Method)相关推荐

  1. [设计模式-行为型]模板方法模式(Template Method)

    一句话 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中. 概括 解析 看过<如何说服女生上床>这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇.打破僵局.展开追求.接吻.前戏.动 ...

  2. 模板方法模式 Template method 行为型 设计模式(二十六)

    模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为你的 ...

  3. java 模板方法_设计模式(java实现)_模板方法模式(Template method)

    设计模式(java实现)_模板方法模式(Template method) 模板方法模式是编程中经常用到到的模式.它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现.这样,新的子类可以在不改变一个 ...

  4. 设计模式的征途—17.模板方法(Template Method)模式

    在现实生活中,很多事情都需要经过几个步骤才能完成,例如请客吃饭,无论吃什么,一般都包含:点单.吃东西.买单等几个步骤,通常情况下这几个步骤的次序是:点单=>吃东西=>买单.在这3个步骤中, ...

  5. 设计模式之模板方法模式(Template Method Pattern)

    模式的定义与特点 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤.它 ...

  6. 【设计模式】行为型02模板方法模式(Template Method Patten)

    五一长假,没有出去,不喜欢嘈杂的人群,玩了会游戏发泄了下憋在心底的戾气,手旁大马克杯里是母亲泡的绿茶.点开自己的播放列表,耳机里传来的是理查德克莱德曼的致爱丽丝.自己是个凡人,卑微渺小的活着.不说废话 ...

  7. 设计模式之模板方法模式(Template Method)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  8. 设计模式之行为模式中的模板方法模式(template method)

    设计模式之行为模式中的模板方法模式(template method) 声明 一.行为模式 二.模板方法模式 三.实例 概况描述 代码 运行结果 优点 类似的模式 优秀文章 声明 本人写此文章只是为了梳 ...

  9. 趣谈设计模式 | 模板方法模式(Template Method):封装不变部分,扩展可变部分

    文章目录 案例:房屋建造 模板方法模式 模板方法模式与策略模式 总结 完整代码与文档 这个设计模式过于简单,所以不是很好举例- 案例:房屋建造 假设我们是建筑公司中的规划者,负责设定建筑方案,在初期我 ...

最新文章

  1. 对机械臂的肩关节与肘关节编码器连接与设置
  2. (初级)数字信号处理目录(不只是目录)
  3. 产品成功之后的品牌策略
  4. [转]Git详解之五 分布式Git
  5. 【笔记】微软OneNote使用笔记,OneNote备份问题
  6. 【转】如何更改VS2010的[默认开发语言]默认环境设置 .
  7. bigdecimal 设置_BigDecimal 使用方法详解
  8. 机器学习1---基本概念
  9. 基于JAVA+SpringMVC+Mybatis+MYSQL的流浪宠物救助系统
  10. html中img显示旋转,css如何实现图片的旋转展示效果(代码示例)
  11. 数据结构视频教程 -《[北大张铭 教学版]数据结构与算法(C++)》
  12. AutoCAD 2019 for Mac 汉化安装手册
  13. 一步步用python制作游戏外挂【转】
  14. 1KB文件夹快捷方式病毒清除(转)
  15. K8S CRD 资源对象删除不掉
  16. linux网络重设,Linux ADSL拨号上网
  17. 低版本的iphone 无法跑在xcode8上
  18. hr 标签可选的属性(续) 和 img 标签介绍
  19. 2019年计算机二级获证条件,2019年下半年全国计算机等级考试报考简章
  20. 计算机windows7更新失败,Win7电脑windows update更新失败如何解决?

热门文章

  1. piechart 文档 android,(Android 应用之路) MPAndroidChart~PieChart
  2. excel如何当计算机使用方法,如何让你的excel文件只准允一台电脑使用,重要资料相当需要...
  3. oracle 朱志辉_《DB2设计、管理与性能优化艺术》(王飞鹏,李玉明,朱志辉,王富国)【摘要 书评 试读】- 京东图书...
  4. bombing:3证书认证系统设计与实现-总体架构
  5. Vulnhub靶机:HARRYPOTTER_ NAGINI
  6. XUbuntu22.04之无法输入中标点符号(一百六十二)
  7. CFileDialog使用方法
  8. 网络安全入门(一)IP欺骗
  9. mysql--使用full join报错
  10. Linux 应用编程之stat 函数