模板方法(Template Method
)模式就是带有模板功能的模式 ,组成模板方法的方法被定义在父类中,这些方法是抽象方法,在模板方法中规定了这些方法的执行流程,这些抽象方法需要子类来具体实现。换句话说,模板方法就是定义好了模板,也就是一定的流程,至于各个抽象方法的具体实现,则有子类们自己决定,所以查看父类的代码是无法知晓这些方法最终会进行何种具体处理,唯一知道的就是父类是如何调用这些方法的。
实现上述这些抽象方法的是子类,在子类中实现了抽象方法也就决定了具体的处理。也就是说,不用的子类中的实现是不同的,当父类的模板方法被调用的时候,处理的方式也就不同,但是值得一提的是,无论子类如何实现抽象方法,如何自定义各自的处理逻辑,它调用父类的模板方法的时候,都会按照父类事先规定好的流程来分别调用这些方法。像这种在父类中定义好处理流程的框架,在子类中实现具体处理
的模式就是模板方法(Template Method
)模式。
在生活中常常能见到类似模板方法模式的案例。比如,我们小时候都练过字帖,我们只要用笔就可以在字帖上临摹出优美的文字出来,看到字帖,在临摹之前就可以知道我们将会写出那些字出来,但是写出来字的效果就得依靠笔的类型,使用毛笔能临摹出粗字体,使用签字笔能临摹出细字体。
在比如,在炒菜的时候,一般步骤都是:往锅里倒油——打开天然气灶——加入具体蔬菜——加入具体调料——出锅
,那么这个流程步骤就是一个模板,我们按照这个流程就可以炒出一盘热腾腾的蔬菜,至于加入的蔬菜和调料是什么类型,那么就得根据自己的口味了。
还有,一般我们玩游戏都有一个具体的步骤:初始化游戏——开始玩游戏——游戏结束
,至于玩的是何种游戏,就可以根据自己的喜好来选择,但是都会遵循这个游戏步骤。这个步骤在模板方法模式中就是对应的模板。后面的示例代码将结合这个问题来对模板方法设计模式进行阐述。
模板方法模式也是一个非常常用的设计模式之一,在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
中一个抽象方法和一个非抽象方法,其中非抽象方法就是模板设计模式中的重要角色——模板。
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
来修饰,它不允许子类来覆盖它,它就是模板方法,它规定了各个抽象方法的执行流程,也就是说,当子类来调用这个模板方法的时候,各个抽象方法实现方式虽然不同,但是他们的执行流程和顺序确实一致的。这个就是模板方法设计模式的基本原理。我们将模板方法设计模式类图设计如下:
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
方法规定了上面三个抽象方法的执行顺序。
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!");
}
}
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
这抽象类,并且重写了三个抽象方法,这正印证了模板方法设计模式的定义:在父类中定义好处理流程的框架,在子类中实现具体处理
。
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
方法的时候,和我们预计想一致,按照指定的顺序将三个可变方法进行了调用,但不同子类的具体实现是不一样的。
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
角色主要负责实现模板方法,并且还负责声明模板方法中用到的抽象方法,这些抽象方法由具体的子类来进行实现,模板方法负责规定这些抽象方法的调用顺序。在本次示例中,该角色由Game
来扮演。
ConcreteClass
角色主要负责实现AbstractClass
角色中声明的抽象方法,不同的ConcreteClass
对这些抽象方法实现的方式不一样的,但是由于在父类中规定了这些抽象方法的调用顺序,所有,即使具体的实现方式不一样,但是最终的执行顺序都是一致的。
模板方法设计模式UML
类图如下所示:
究竟使用模板方法模式可以给我们的代码带来什么好处呢?它的主要优点就是在父类中编写好了算法,在子类中无需重复编写,如果算法有问题,那么只需要修改父类中模板方法即可。
还有重要的一点就是,在使用父类类型变量保存子类实例对象的时候,无需使用instanceof
等指定子类的具体类型,也可以直接调用模板方法。