1、简介

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

假设,你们公司是做模型生产的,做过桥梁模型、建筑模型、机械模型。本次客户需要做一个悍马模型,这是你们公司第一次做车辆模型。

时间有限,任务量巨大,领导说先不考虑拓展性。那就先按照一般的经验设计图:

非常的简单实现,悍马车有两个型号,H1和H2。按照需求,只需要悍马模型,那就先写个抽象类,然后定义两个不同型号的实现类。通过简单的继承就可以实现业务的需求。
(1)抽象类

public abstract class HummerModel {/** 首先, 这个模型要能够被发动起来, 别管是手摇发动, 还是电力发动, 反正是要能够发动起来, 那这个实现要在实现类里了*/public abstract void start();//能发动, 还要能停下来, 那才是真本事public abstract void stop();//喇叭会出声音, 是滴滴叫, 还是哔哔叫public abstract void alarm();//引擎会轰隆隆地响, 不响那是假的public abstract void engineBoom();//那模型应该会跑吧, 别管是人推的, 还是电力驱动的, 总之要会跑public abstract void run();
}

在抽象类中,我们定义了悍马模型都必须具有的特质:能够发动、停止, 喇叭会响,引擎可以轰鸣,而且还可以停止。但是每个型号的悍马实现是不同的, H1型号的悍马如代码:
(2)实现类

public class HummerH1Model extends HummerModel {//H1型号的悍马车鸣笛public void alarm() {System.out.println("悍马H1鸣笛...");}//引擎轰鸣声public void engineBoom() {System.out.println("悍马H1引擎声音是这样的...");}//汽车发动public void start() {System.out.println("悍马H1发动...");}//停车public void stop() {System.out.println("悍马H1停车...");}//开动起来public void run(){//先发动汽车this.start();//引擎开始轰鸣this.engineBoom();//然后就开始跑了, 跑的过程中遇到一条狗挡路, 就按喇叭this.alarm();//到达目的地就停车this.stop();}
}

虽然H2模型没写,但是大家可以知道,两个实现类的run()方法是完全相同的,那这个run()方法的实现应该出现在抽象类,不应该在实现类上,抽象是所有子类的共性封装。

**注意:**在软件开发过程中,如果相同的一段代码复制过两次,就需要对设计产生怀疑,架构师要明确地说明为什么相同的逻辑要出现两次或更多次。

根据上述描述修改类图如下:

抽象类HummerModel中的run()方法,由抽象方法变更为实现方法,其源代码如下:

public abstract class HummerModel {/*
* 首先, 这个模型要能发动起来, 别管是手摇发动, 还是电力发动, 反正是要能够发动起来, 那这个实现要在实现类里了
*/
public abstract void start();
//能发动, 还要能停下来, 那才是真本事
public abstract void stop();
//喇叭会出声音, 是滴滴叫, 还是哔哔叫
public abstract void alarm();
//引擎会轰隆隆地响, 不响那是假的
public abstract void engineBoom();
//那模型应该会跑吧, 别管是人推的, 还是电力驱动, 总之要会跑
public void run(){//先发动汽车this.start();//引擎开始轰鸣this.engineBoom();//然后就开始跑了, 跑的过程中遇到一条狗挡路, 就按喇叭this.alarm();//到达目的地就停车this.stop();
}
}

在抽象的悍马模型上已经定义了run()方法的执行规则,它的两个具体实现类就不需要实现run()方法了。
(3)场景类

public class Client {public static void main(String[] args) {//XX公司要H1型号的悍马HummerModel h1 = new HummerH1Model();//H1模型演示h1.run();}
}

目前客户只要看H1型号的悍马车,没问题,生产出来,同时可以运行起来给他看看。非常简单,这就是模板方法模式。

2、推荐实现案例

模版模式的通用类图:

模板方法模式确实非常简单,仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式。其中,AbstractClass叫做抽象模板,它的方法分为两类:

  • 基本方法
    基本方法也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。

  • 模版方法
    可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。

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

在类图中还有一个角色:具体模板。ConcreteClass1和ConcreteClass2属于具体模板,实现父类所定义的一个或多个抽象方法,也就是父类定义的基本方法在子类中得以实现。

(1)抽象模版类

public abstract class AbstractClass {//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//模板方法
public void templateMethod(){/** 调用基本方法, 完成相关的逻辑*/this.doAnything();this.doSomething();}
}

(2)具体模版类

public class ConcreteClass1 extends AbstractClass {//实现基本方法protected void doAnything() {//业务逻辑处理}protected void doSomething() {//业务逻辑处理}
}public class ConcreteClass2 extends AbstractClass {//实现基本方法protected void doAnything() {//业务逻辑处理}protected void doSomething() {//业务逻辑处理}
}

(3)场景类

public class Client {public static void main(String[] args) {AbstractClass class1 = new ConcreteClass1();AbstractClass class2 = new ConcreteClass2();//调用模板方法class1.templateMethod();class2.templateMethod();}
}

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

3、优缺点

3.1 优点

  1. 封装不变部分,扩展可变部分
    把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。在悍马模型例子中,是不是就非常容易扩展?例如增加一个H3型号的悍马模型,很容易呀,增加一个子类,实现父类的基本方法就可以了。
  2. 提取公共部分代码,便于维护
    我们例子中刚刚走过的弯路就是最好的证明,如果我们不抽取到父类中, 任由这种散乱的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码!
  3. 行为由父类控制,子类实现
    基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

3.2 缺点

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

4、应用场景

  • 多个子类有公有的方法, 并且逻辑基本相同时。
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

5、注意事项

6、拓展

到目前为止,这两个模型都稳定地运行,突然有一天,老大急匆匆地找到了我:“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了!客户提出H1型号的悍马喇叭想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”自己惹的祸,就要想办法解决它,稍稍思考一下,解决办法有了,先画出类图:

类图改动似乎很小,在抽象类HummerModel中增加了一个实现方法isAlarm,确定各个型号的悍马是否需要声音,由各个实现类覆写该方法,同时其他的基本方法由于不需要对外提供访问,因此也设计为protected类型。

(1)扩展后的抽象模板类

public abstract class HummerModel {/*
* 首先, 这个模型要能够被发动起来, 别管是手摇发动, 还是电力发动, 反正是要能够发动起来, 那这个实现要在实现类里了
*/
protected abstract void start();
//能发动, 还要能停下来, 那才是真本事
protected abstract void stop();
//喇叭会出声音, 是滴滴叫, 还是哔哔叫
protected abstract void alarm();
//引擎会轰隆隆的响, 不响那是假的
protected abstract void engineBoom();
//那模型应该会跑吧, 别管是人推的, 还是电力驱动, 总之要会跑
final public void run() {//先发动汽车
this.start();
//引擎开始轰鸣
this.engineBoom();
//要让它叫的就是就叫, 喇嘛不想让它响就不响
if(this.isAlarm()){this.alarm();
}
//到达目的地就停车
this.stop();
}
//钩子方法, 默认喇叭是会响的
protected boolean isAlarm(){return true;
}
}

在抽象类中,isAlarm是一个实现方法。其作用是模板方法根据其返回值决定是否要响喇叭,子类可以覆写该返回值,由于H1型号的喇叭是想让它响就响,不想让它响就不响,由人控制,其源代码如代码:
(2)扩展后的H1和H2实现类

public class HummerH1Model extends HummerModel {private boolean alarmFlag = true; //要响喇叭
protected void alarm() {System.out.println("悍马H1鸣笛...");
}
protected void engineBoom() {System.out.println("悍马H1引擎声音是这样的...");
}
protected void start() {System.out.println("悍马H1发动...");
}
protected void stop() {System.out.println("悍马H1停车...");
}
protected boolean isAlarm() {return this.alarmFlag;
}
//要不要响喇叭, 是由客户来决定的
public void setAlarm(boolean isAlarm){this.alarmFlag = isAlarm;
}
}

只要调用H1型号的悍马,默认是有喇叭响的,当然你可以不让喇叭响, 通过isAlarm(false)就可以实现。 H2型号的悍马是没有喇叭声响的:

public class HummerH2Model extends HummerModel {protected void alarm() {System.out.println("悍马H2鸣笛...");
}
protected void engineBoom() {System.out.println("悍马H2引擎声音是这样的...");
}
protected void start() {System.out.println("悍马H2发动...");
}
protected void stop() {System.out.println("悍马H2停车...");
}
//默认没有喇叭的
protected boolean isAlarm() {return false;
}
}

H2型号的悍马设置isAlarm()的返回值为false,也就是关闭了喇叭功能。场景类代码如代码:
(3)拓展后的场景类

public class Client {public static void main(String[] args) throws IOException {System.out.println("-------H1型号悍马--------");System.out.println("H1型号的悍马是否需要喇叭声响? 0-不需要 1-需要");String type=(new BufferedReader(new InputStreamReader(System.in))).read();HummerH1Model h1 = new HummerH1Model();if(type.equals("0")){h1.setAlarm(false);}h1.run();System.out.println("\n-------H2型号悍马--------");HummerH2Model h2 = new HummerH2Model();h2.run();}
}

H1型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结果,该方法就叫做钩子方法(HookMethod)。有了钩子方法模板方法模式才算完美,大家可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!

模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法,具体到前面那个例子,就是run()方法按照规定的顺序(先调用start(),然后再调用engineBoom(),再调用alarm(),最后调用stop())调用本类的其他方法,并且由isAlarm()方法的返回值确定run()中的执行顺序变更。

7、最佳实践

初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”。这个问题很有普遍性,反正我是被问过好几回,那么父类是否可以调用子类的方法呢?我的回答是能,但强烈地、极度地不建议这么做,那该怎么做呢?
●把子类传递到父类的有参构造中,然后调用。
●使用反射的方式调用,你使用了反射还有谁不能调用的?!
●父类调用子类的静态方法。

这三种都是父类直接调用子类的方法,好用不?好用!解决问题了吗?解决了!项目中允许使用不?不允许!我就一直没有搞懂为什么要用父类调用子类的方法。如果一定要调用子类,那为什么要继承它呢?搞不懂。其实这个问题可以换个角度去理解,父类建立框架,子类在重写了父类部分的方法后,再调用从父类继承的方法,产生不同的结果(而这正是模板方法模式)。这是不是也可以理解为父类调用了子类的方法呢?你修改了子类,影响了父类行为的结果,曲线救国的方式实现了父类依赖子类的场景,模板方法模式就是这种效果。

模板方法在一些开源框架中应用非常多,它提供了一个抽象类,然后开源框架写了一堆子类。如果你需要扩展功能,可以继承这个抽象类,然后覆写protected方法,再然后就是调用一个类似execute方法,就完成你的扩展开发,非常容易扩展的一种模式。

Java设计模式——模版方法模式相关推荐

  1. 设计模式 模版方法模式 展现程序员的一天

    继续设计模式~ 模版方法模式 老套路,先看下定义:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤. 简单看下定义,模版方法定义了一个算 ...

  2. 设计模式——模版方法模式详解(论沉迷LOL对学生的危害)

    0. 前言 写在最前面,本人的设计模式类博文,建议先看博文前半部分的理论介绍,再看后半部分的实例分析,最后再返回来复习一遍理论介绍,这时候你就会发现我在重点处标红的用心,对于帮助你理解设计模式有奇效哦 ...

  3. JAVA设计模式 - 工厂方法模式

    工厂方法模式(Factory Pattern) 是一种创建型设计模式 , 它是Java中最常用的设计模式之一 . 1 . 工厂方法模式的定义 定义一个创建对象的接口 , 让子类决定实例化哪一个类 . ...

  4. Java设计模式--工厂方法模式

    工厂方法模式 1.工厂方法模式的概述 1.1工厂模式是简单工厂模式的延伸,符合开闭原则的基础上,还可以在增加新的具体产品对象时不需要对已有系统做任何修改. 1.2工厂方法模式:定义一个用于创建对象的接 ...

  5. 《转》java设计模式--工厂方法模式(Factory Method)

    本文转自:http://www.cnblogs.com/archimedes/p/java-factory-method-pattern.html 工厂方法模式(别名:虚拟构造) 定义一个用于创建对象 ...

  6. Java设计模式-工厂方法模式和抽象工厂模式

    工厂方法模式定义: 即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)."一对一"的关系 1,一抽象工厂类派生出多个具体工厂类: 2, ...

  7. Java设计模式—工厂方法模式抽象工厂模式

    工厂方法模式与抽象工厂模式都是设计模式中重要而且常见的模式.       工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 通用类图如下: 在 ...

  8. Java设计模式-工厂方法模式的使用和介绍

    一.工厂方法模式简介 1.定义 工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorph ...

  9. java设计模式---工厂方法模式

    简单工厂模式的应用就是用来创建一个对象,简单工厂模式是将所有对象的初始化集于一个具体的工厂类来实现,这个工厂类负责所有产品的创建,但是这种创建只能创建单一的产品,如简单工厂模式里的ChickenFac ...

最新文章

  1. matlab变压器紧耦合,一种紧耦合高效llc谐振变压器的制造方法
  2. 原创:去繁存简,回归本源:微信小程序公开课信息分析《一》
  3. 人工智能如何落地安防?需先迈过算力这一关
  4. Android通过透明度设置背景变暗
  5. object - c 函数的值
  6. python 线型_CCF 202006-1 线性分类器 python
  7. 数据结构之自建算法库——链栈
  8. python源码多平台编译_提升Python程序运行效率的6个方法
  9. FCPX插件:半调图像高级马赛克特效Yanobox Mosaic
  10. portainer安装_Docker 图形化工具—Portainer
  11. springboot yml对于list列表配置方式
  12. 梦断代码最后4章读后感
  13. WPS Excel VB宏简单编程
  14. 六、3D数学矩阵线性变换
  15. 创意视觉应用︱基于深度学习的CVaaS计算机视觉即服务案例(Computer Vision as a Service)
  16. 数学建模——人口增长模型的matlab实现以及对2010年人口预测
  17. 信息学奥赛一本通1011
  18. tumblr android app,6 Best Tumblr Apps for Android and iOS (2018)
  19. 【Nginx 源码学习】平滑重启,源码追踪
  20. QT5的软键盘输入法实现

热门文章

  1. uni-app自定义规范
  2. vue.js和vue.main.js下载地址
  3. e.target.dataset和e.currentTarget.dataset
  4. MySQL中主键和unique的区别
  5. ios开发中如何调用苹果自带地图导航
  6. Line云端全自动加好友机器人
  7. 微信是如何做用户体验的?
  8. 生命的节日,散文一篇
  9. linux 原型软件,7款免费原型设计工具,总有一款是你的菜!
  10. 莫纳什大学计算机硕士专业怎么样,澳大利亚留学:莫纳什大学计算机硕士的14个专业...