## 模板方法模式 > 模板方法模式属于类的行为模式。其核心是定义一个操作中的算法骨架,而将一些步骤延迟到子类中。 #### 定义 模板方法(`Template Method`)模式就是带有模板功能的模式 ,组成模板方法的方法被定义在父类中,这些方法是抽象方法,在模板方法中规定了这些方法的执行流程,这些抽象方法需要子类来具体实现。换句话说,模板方法就是定义好了模板,也就是一定的流程,至于各个抽象方法的具体实现,则有子类们自己决定,所以查看父类的代码是无法知晓这些方法最终会进行何种具体处理,唯一知道的就是父类是如何调用这些方法的。 实现上述这些抽象方法的是子类,在子类中实现了抽象方法也就决定了具体的处理。也就是说,不用的子类中的实现是不同的,当父类的模板方法被调用的时候,处理的方式也就不同,但是值得一提的是,无论子类如何实现抽象方法,如何自定义各自的处理逻辑,它调用父类的模板方法的时候,都会按照父类事先规定好的流程来分别调用这些方法。像这种`在父类中定义好处理流程的框架,在子类中实现具体处理`的模式就是模板方法(`Template Method`)模式。 #### 问题引入 在生活中常常能见到类似模板方法模式的案例。比如,我们小时候都练过字帖,我们只要用笔就可以在字帖上临摹出优美的文字出来,看到字帖,在临摹之前就可以知道我们将会写出那些字出来,但是写出来字的效果就得依靠笔的类型,使用毛笔能临摹出粗字体,使用签字笔能临摹出细字体。 在比如,在炒菜的时候,一般步骤都是:`往锅里倒油——打开天然气灶——加入具体蔬菜——加入具体调料——出锅`,那么这个流程步骤就是一个模板,我们按照这个流程就可以炒出一盘热腾腾的蔬菜,至于加入的蔬菜和调料是什么类型,那么就得根据自己的口味了。 还有,一般我们玩游戏都有一个具体的步骤:`初始化游戏——开始玩游戏——游戏结束`,至于玩的是何种游戏,就可以根据自己的喜好来选择,但是都会遵循这个游戏步骤。这个步骤在模板方法模式中就是对应的模板。后面的示例代码将结合这个问题来对模板方法设计模式进行阐述。 #### 模板方法设计模式在JDK源码中的应用 模板方法模式也是一个非常常用的设计模式之一,在`JDK`源码中,就存在大量的模板方法设计模式的身影,比如: * `java.io.InputStream`, `java.io.OutputStream`, `java.io.Reader`以及`java.io.Writer`中所有非抽象方法。 * `java.util.AbstractList`, `java.util.AbstractSet`以及`java.util.AbstractMap`中所有非抽象方法。 接下来,我们一起来阅读`java.io.InputStream`的部分源码,来感受一下模板方法设计模式是如何在`JDK`中应用的。这里列举`java.io.InputStream`中一个抽象方法和一个非抽象方法,其中非抽象方法就是模板设计模式中的重要角色——模板。 ~~~javascript public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } public abstract int read() throws IOException; ~~~ 上面的非抽象方法给定了从输入流中读取数据的具体流程,而如何从输入流中读取,却没有给出具体的实现方法,需要`java.io.InputStream`这个抽象类的子类来具体实现read方法。 #### 手动实现模板方法设计模式 也许阅读到这里,你对模板方法设计模式还没有一个清晰的认识,没关系,接下来将从最简单的示例开始,来展现模板方法设计模式的基本用法和原理。 我们选择上面问题引入中的“玩游戏”,使用代码来具体实现模板方法设计模式。首先,我们需要一个抽象类,这个抽象类有可变内容和不可变内容,可变内容就是该抽象类拥有抽象方法,这就需要子类去实现它,不同的子类对其实现方式往往是不同的;不可变内容就是该抽象类拥有非抽象方法,这个抽象方法往往是由`final`来修饰,它不允许子类来覆盖它,它就是模板方法,它规定了各个抽象方法的执行流程,也就是说,当子类来调用这个模板方法的时候,各个抽象方法实现方式虽然不同,但是他们的执行流程和顺序确实一致的。这个就是模板方法设计模式的基本原理。我们将模板方法设计模式类图设计如下: ![](https://ask.qcloudimg.com/http-save/yehe-2413148/hf8y7z6twy.png?imageView2/2/w/1620) * **抽象类Game** ~~~javascript package cn.itlemon.design.pattern.chapter03.template.method; /** * 模板方法设计模式主要抽象类 * * @author jiangpingping * @date 2018/9/14 下午3:22 */ public abstract class Game { /** * 初始化游戏 */ public abstract void initialize(); /** * 开始游戏 */ public abstract void startPlay(); /** * 结束游戏 */ public abstract void endPlay(); /** * 模板方法:确定了游戏的流程 */ public final void playGame() { initialize(); startPlay(); endPlay(); } } ~~~ 注意观察这个抽象类,它有可变内容`initialize`、`startPlay`、`endPlay`三个抽象方法,有一个不可变内容`playGame`方法,其中不可变内容`playGame`方法规定了上面三个抽象方法的执行顺序。 * **子类BasketBallGame** ~~~javascript package cn.itlemon.design.pattern.chapter03.template.method; /** * 篮球游戏? * * @author jiangpingping * @date 2018/9/14 下午3:27 */ public class BasketBallGame extends Game { @Override public void initialize() { System.out.println("Basketball Game Initialized! Start playing."); } @Override public void startPlay() { System.out.println("Basketball Game Started. Enjoy the game!"); } @Override public void endPlay() { System.out.println("Basketball Game Finished!"); } } ~~~ * **子类FootBallGame** ~~~javascript package cn.itlemon.design.pattern.chapter03.template.method; /** * 足球游戏⚽️ * * @author jiangpingping * @date 2018/9/14 下午3:30 */ public class FootBallGame extends Game { @Override public void initialize() { System.out.println("Football Game Initialized! Start playing."); } @Override public void startPlay() { System.out.println("Football Game Started. Enjoy the game!"); } @Override public void endPlay() { System.out.println("Football Game Finished!"); } } ~~~ 这两个子类都继承了`Game`这抽象类,并且重写了三个抽象方法,这正印证了模板方法设计模式的定义:`在父类中定义好处理流程的框架,在子类中实现具体处理`。 * **测试类Main** ~~~javascript package cn.itlemon.design.pattern.chapter03.template.method; /** * @author jiangpingping * @date 2018/9/14 下午3:32 */ public class Main { public static void main(String[] args) { Game basketBallGame = new BasketBallGame(); Game footBallGame = new FootBallGame(); basketBallGame.playGame(); System.out.println(); footBallGame.playGame(); } } ~~~ 在该测试类中,我们分别创建了两个子类的对象,并将这两个对象保存在父类的变量中,当分别调用`playGame`方法的时候,和我们预计想一致,按照指定的顺序将三个可变方法进行了调用,但不同子类的具体实现是不一样的。 ~~~javascript Basketball Game Initialized! Start playing. Basketball Game Started. Enjoy the game! Basketball Game Finished! Football Game Initialized! Start playing. Football Game Started. Enjoy the game! Football Game Finished! ~~~ #### 浅析模板方法模式中的重要角色 在模板方法设计模式中,主要角色只有两个,分别是:描述抽象方法和模板方法的抽象类,以及实现抽象方法的具体子类。 * **AbstractClass(抽象类)** `AbstractClass`角色主要负责实现模板方法,并且还负责声明模板方法中用到的抽象方法,这些抽象方法由具体的子类来进行实现,模板方法负责规定这些抽象方法的调用顺序。在本次示例中,该角色由`Game`来扮演。 * **ConcreteClass(具体类)** `ConcreteClass`角色主要负责实现`AbstractClass`角色中声明的抽象方法,不同的`ConcreteClass`对这些抽象方法实现的方式不一样的,但是由于在父类中规定了这些抽象方法的调用顺序,所有,即使具体的实现方式不一样,但是最终的执行顺序都是一致的。 #### 模板方法模式UML类图 模板方法设计模式`UML`类图如下所示: ![](https://ask.qcloudimg.com/http-save/yehe-2413148/136bopks3w.png?imageView2/2/w/1620) #### 为什么要使用模板方法模式 究竟使用模板方法模式可以给我们的代码带来什么好处呢?它的主要优点就是在父类中编写好了算法,在子类中无需重复编写,如果算法有问题,那么只需要修改父类中模板方法即可。 还有重要的一点就是,在使用父类类型变量保存子类实例对象的时候,无需使用`instanceof`等指定子类的具体类型,也可以直接调用模板方法。