什么是模板方法模式?

定义一个操作中算法的骨架,而将这些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

好抽象的概念啊,文绉绉的东西就是不讨人喜欢,下面我用一个生活中常见的例子来举例说明吧

上图是个饮料机,大家都很熟悉吧,各大商场随处可见的一个东西,这里举个例子,比如我们想喝某种饮料,只要按下对应的饮料类别,饮料就自动出来了。

这里我们可以抽象化的想象下饮料在机器里的制作过程(这里只是简单举例,别钻牛角尖哈)

大致我们可以分成4个步骤

①烧水  ②冲泡饮料  ③把饮料倒入杯中  ④加入调料

例如:

咖啡:烧开热水-->加入咖啡粉冲泡-->把饮料倒入杯中-->加入少许糖

奶茶:烧开热水-->加入奶茶粉冲泡-->把饮料加入杯中-->加入椰果/珍珠

不难发现,饮料制作过程中的步骤中的①烧水、③把饮料倒入杯中是重复工作,制泡哪种饮料都一样,那么也就是重复工作,我们可以把它设定为通用性操作。

我们只需要去关心步骤②和步骤④即可

由于制泡饮料的步骤就是这4步,所以我们可以把它抽象成一个"制作饮料模板"出来,下面就以上面这个例子,我用代码来说明

DrinkTemplate.java(模板类)

这是一个制作饮料的模板类,也就是制作所有饮料的基类

我们可以把这4个步骤封装到一个模板方法里,并实现里面的通用步骤

由于避免继承它的子类去修改整体制作架构,所以这个方法用了final修饰符来修饰,好比著名的"好莱坞原则":Don't call us, we'll call you 子类需要听从父类的安排

由于步骤②和步骤④需要根据具体制泡的饮料来确定,所以需要延迟到子类去实现,这里采用了protected修饰符以便子类可以复写,其他方法就可以直接写"死"掉,用private修饰符修饰,这样的使得代码工作人员能够更加关注自身的工作,而不必去考虑一些其他因素。

  1. package com.lcw.template.test;
  2.  
  3. public abstract class DrinkTemplate {
  4.  
  5. /**抽象基类
  6. *
  7. * 制作饮料方法模板
  8. * 4个步骤 1、烧水 2、冲泡饮料 3、把饮料倒入杯中 4、加调料
  9. * 由于步骤1、3是通用的步骤,适合于制作任何饮料,所以可以把它写死
  10. * 2和4步骤,针对不同的饮料有不同的选择,所以可以把它延迟到子类去复写实现(注意访问修饰符)
  11. */
  12. public final void drinkTempLate(){
  13. boilWater();//烧水
  14. brew();//冲泡饮料
  15. pourInCup();//把饮料倒入杯中
  16. addCondiments();//加调料
  17. }
  18.  
  19. protected abstract void addCondiments();//加调料,由于饮料所加调料各不相同,所以可以延迟到子类实现
  20.  
  21. private void pourInCup() {
  22. System.out.println("把饮料倒入杯中...");
  23. }
  24.  
  25. protected abstract void brew();//冲泡饮料 ,由于饮料所用的材料各不相同,所以可以延迟到子类实现
  26.  
  27. private void boilWater() {
  28. System.out.println("烧水步骤进行中...");
  29. }
  30. }

MakeCoffee.java(冲泡咖啡类)

这个没啥好说的,就是继承了抽象基类,并复写了它的抽象方法

  1. package com.lcw.template.test;
  2. /**
  3. *
  4. * @author Balla_兔子
  5. * 冲泡咖啡
  6. *
  7. */
  8. public class MakeCoffee extends DrinkTemplate {
  9.  
  10. @Override
  11. protected void addCondiments() {
  12. System.out.println("加糖...");
  13. }
  14.  
  15. @Override
  16. protected void brew() {
  17. System.out.println("加入咖啡粉冲泡...");
  18. }
  19.  
  20. }

MakeMilkTea.java(冲泡奶茶类)

  1. package com.lcw.template.test;
  2. /**
  3. * 冲泡奶茶
  4. * @author Balla_兔子
  5. *
  6. */
  7. public class MakeMilkTea extends DrinkTemplate {
  8.  
  9. @Override
  10. protected void addCondiments() {
  11. System.out.println("加椰果...");
  12. }
  13.  
  14. @Override
  15. protected void brew() {
  16. System.out.println("加入奶茶粉冲泡...");
  17. }
  18.  
  19. }

Test.java(测试类)

  1. package com.lcw.template.test;
  2.  
  3. public class Test {
  4.  
  5. /**
  6. * @author Balla_兔子
  7. */
  8. public static void main(String[] args) {
  9. DrinkTemplate coffee=new MakeCoffee();
  10. coffee.drinkTempLate();
  11. System.out.println("*******************************");
  12. DrinkTemplate milkTea=new MakeMilkTea();
  13. milkTea.drinkTempLate();
  14. }
  15.  
  16. }

看下运行效果:

哈哈,这样的实现类写起来是不是很清晰明了啊,只需要去复写我们需要关心的方法即可,大大提高了代码的复用性。

但这里有个问题就暴露出来了,冲泡咖啡的实现固然没错,但总有些人喝咖啡是不加糖的,这是该怎么办呢?

这里就引入了一个"钩子"hook概念

我们可以在某个具体实现方法前后分别加入钩子,就好比是前置方法或者后置方法,就像日志技术一样,在每完成一个业务动作前都需要记录日志

而这个前置方法,我们可以利用一个布尔来做判断,并给它一个默认,来看看具体实现方法

DrinkTemplate.java(模板类)

  1. package com.lcw.template.test;
  2.  
  3. public abstract class DrinkTemplate {
  4.  
  5. /**抽象基类
  6. *
  7. * 制作饮料方法模板
  8. * 4个步骤 1、烧水 2、冲泡饮料 3、把饮料倒入杯中 4、加调料
  9. * 由于步骤1、3是通用的步骤,适合于制作任何饮料,所以可以把它写死
  10. * 2和4步骤,针对不同的饮料有不同的选择,所以可以把它延迟到子类去复写实现(注意访问修饰符)
  11. */
  12. public final void drinkTempLate(){
  13. boilWater();//烧水
  14. brew();//冲泡饮料
  15. pourInCup();//把饮料倒入杯中
  16. if(condition()==true){//若条件允许,则加入调料,默认允许
  17. addCondiments();//加调料
  18. }
  19. }
  20.  
  21. protected boolean condition() {
  22. return true;
  23. }
  24.  
  25. protected abstract void addCondiments();//加调料,由于饮料所加调料各不相同,所以可以延迟到子类实现
  26.  
  27. private void pourInCup() {
  28. System.out.println("把饮料倒入杯中...");
  29. }
  30.  
  31. protected abstract void brew();//冲泡饮料 ,由于饮料所用的材料各不相同,所以可以延迟到子类实现
  32.  
  33. private void boilWater() {
  34. System.out.println("烧水步骤进行中...");
  35. }
  36. }

Test.java(测试类)

  1. package com.lcw.template.test;
  2.  
  3. public class Test {
  4.  
  5. /**
  6. * @author Balla_兔子
  7. */
  8. public static void main(String[] args) {
  9. DrinkTemplate coffee=new MakeCoffee();
  10. coffee.drinkTempLate();
  11. System.out.println("咖啡制作完毕!");
  12. System.out.println("*******************************");
  13. DrinkTemplate milkTea=new MakeMilkTea();
  14. milkTea.drinkTempLate();
  15. System.out.println("奶茶制作完毕!");
  16. }
  17.  
  18. }

看下这次的效果,哈哈,无糖咖啡出炉~

总结下:

先说说模板方法模式的优点:

1、封装性好  2、复用性好、  3、屏蔽细节  4、便于维护

至于缺点呢,就是继承问题,在JAVA里只能继承一个父类。

流程: 分析场景-->步骤抽取-->重构代码-->重要、复杂的算法,核心算法设计为模版

注意点:

模版方法需要声明成 public final

private方法是基本逻辑

protect abstract 方法是可扩展方法

钩子使得模板方法更加灵活

软件设计模式之模板方法模式(JAVA)的更多相关文章

  1. 设计模式之模板方法模式(Java实现)

    "那个,上次由于我老婆要给我做饭,所以就没有说完就走掉了...这个那个".这次和以前一样,先来开场福利(工厂方法模式已被作者踹下场).由美女抽象工厂介绍一下适用场景~大家欢迎 抽象 ...

  2. 软件设计模式之代理模式(JAVA)

    貌似停笔了近半个月了,实在不该啊,新的一年,时刻让自己归零. Back To Zero,就从这篇文章拉开今年的序幕吧. 这篇文章准备介绍下有关代理模式的基本概念和静态代理.动态代理的优缺点及使用方法( ...

  3. 软件设计模式之工厂模式(JAVA)

    什么是工厂模式? 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式.著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见.因为工厂模式就相 ...

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

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

  5. 设计模式之第3章-模板方法模式(Java实现)

    设计模式之第3章-模板方法模式(Java实现) "那个,上次由于我老婆要给我做饭,所以就没有说完就走掉了...这个那个".这次和以前一样,先来开场福利(工厂方法模式已被作者踹下场) ...

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

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

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

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

  8. Java设计模式应用——模板方法模式

    所谓模板方法模式,就是在一组方法结构一致,只有部分逻辑不一样时,使用抽象类制作一个逻辑模板,具体是实现类仅仅实现特殊逻辑就行了.类似科举制度八股文,文章结构相同,仅仅具体语句有差异,我们只需要按照八股 ...

  9. Java设计模式之模板方法模式(Template)

    前言: 我们在开发中有很多固定的流程,这些流程有很多步凑是固定的,比如JDBC中获取连接,关闭连接这些流程是固定不变的,变动的只有设置参数,解析结果集这些是根据不同的实体对象“来做调整”,针对这种拥有 ...

随机推荐

  1. [转载]CMMI之功能点估算法:EI、EQ和EO

    EI.EO.EQ EI是处理来自于应用程序边界外部的一组数据的输入,它的主要目的是维护一个或多个ILF,以及/或者更改系统的行为. EO是输送数据到应用程序边界外部的过程.它的主要目的是通过逻辑处理过 ...

  2. java中判断字节数组的编码方式是不是UTF-8

    1,用google的工具包,配置maven: <!-- https://mvnrepository.com/artifact/com.googlecode.juniversalchardet/j ...

  3. C++类默认函数

    问题,which is true??? 每个类都有一个无参构造函数 每个类都有一个拷贝构造函数 每个类可以有多个构造函数 每个类可以多个析构函数 默认构造函数   析构函数   拷贝构造函数   赋值 ...

  4. git pull出现There is no tracking information for the current branch

    使用git pull 或者 git push 的时候报错 gitThere is no tracking information for the current branch. Please spec ...

  5. C语言 · 陶陶摘苹果2

    算法提高 陶陶摘苹果2   时间限制:1.0s   内存限制:256.0MB      问题描述 陶陶家的院子里有一棵苹果树,每到秋天树上就会结出n个苹果.苹果成熟的时候,陶陶就会跑去摘苹果.陶陶有个 ...

  6. 简析TCP的三次握手与四次分手<转>

    TCP是什么? 具体的关于TCP是什么,我不打算详细的说了:当你看到这篇文章时,我想你也知道TCP的概念了,想要更深入的了解TCP的工作,我们就继续.它只是一个超级麻烦的协议,而它又是互联网的基础,也 ...

  7. DbgPrint/KdPrint输出格式控制

    在驱动编程学习中,往往需要通过DbgPrint或者KdPrint来输出调试信息,对于Check版本,KdPrint只是DbgPrint的一个宏定义,而对于Free版本,KdPrint将被优化掉.这些输 ...

  8. Hadoop与Spark之间的比较

    Hadoop与Spark之间的比较 Hadoop框架的主要模块包括如下: Hadoop Common Hadoop分布式文件系统(HDFS) Hadoop YARN Hadoop MapReduce ...

  9. [转]Java动态代理

    动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...

  10. 【驱动】——错误: 初始值设定项里有未知的字段‘ioctl’

    这个错误网上搜索发现3.0.0.15版本内核 file_operation结构体已经删除了ioctl函数,取代的是: long (*unlocked_ioctl) (struct file *, un ...