Template method design pattern
模版方法模式的概念、模版方法模式的结构、模版方法模式的优缺点、模版方法模式的使用场景、模版方法模式的实现示例、模版方法模式的源码分析
1、模版方法模式的概念
模版方法模式,即定义一个算法骨架,而将一些步骤延迟到子类中实现,使得子类可以在不改变算法骨架的情况下重新定义该算法的某些特定步骤。
2、模版方法模式的结构
- 抽象类:负责定义一个算法的轮廓和骨架。由一个模版方法和若干个基本方法构成。
- 模版方法:定义了算法的骨架,按照某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模版方法的组成部分。基本方法又可以分为三种:
- 抽象方法:算法中的可变部分,由子类实现。
- 基本方法:算法中的不变部分,也可以由子类进行重写覆盖。
- 钩子方法:在抽象类中实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。钩子方法一般用于判断,如 isXXX 等。
- 具体子类:继承抽象类,实现其所定义的抽象方法和钩子方法。
3、模版方法模式的优缺点
- 优点:
- 提高代码的复用性。将相同代码放在抽象父类中,将不同代码放入不同的子类中。
- 实现了反向控制。通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并满足开闭原则。
- 缺点:
- 对每个不同的实现都需要定义一个子类,这会导致系统更加庞大,类爆炸。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,提高了代码的阅读难度。
4、模版方法的使用场景
- 算法的整体步骤很固定,但其中个别部分易变时,可使用模版方法,将易变的部分抽象化,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
5、模版方法的实现示例
抽象父类:
public abstract class GameAbstract {
/**
* 模版方法
*/
public final void playGame() {
this.openGame();
this.selectHero();
this.attack();
this.closeGame();
}
/**
* 基本方法-具体方法
*/
public void openGame() {
System.out.println("打开游戏");
}
/**
* 基本方法-抽象方法
*/
public abstract void selectHero();
public abstract void attack();
public void closeGame() {
System.out.println("关闭游戏");
}
}
具体子类一:
public class OnePlayer extends GameAbstract {
@Override
public void selectHero() {
System.out.println("影流之主");
}
@Override
public void attack() {
System.out.println("禁奥义·瞬狱影杀阵");
}
}
具体子类二:
public class TwoPlayer extends GameAbstract {
@Override
public void selectHero() {
System.out.println("潮汐海灵");
}
@Override
public void attack() {
System.out.println("巨鲨来袭");
}
}
测试:
public class TemplateMethodTest {
public static void main(String[] args) {
GameAbstract onePlayer = new OnePlayer();
onePlayer.playGame();
GameAbstract twoPlayer = new TwoPlayer();
twoPlayer.playGame();
}
}
测试结果:
打开游戏
影流之主
禁奥义·瞬狱影杀阵
关闭游戏
打开游戏
潮汐海灵
巨鲨来袭
关闭游戏
6、模版方法的源码分析
jdk 中的 InputStream 类的设计就使用了模版方法。在 InputStream 类中定义了多个 read 方法,无参的 read 是抽象方法,要求子类必须实现。而 read(byte b[]) 调用了 read(byte b[], int off, int len) 方法,所以此处 read(byte b[], int off, int len) 方法是模版方法,其定义了算法的步骤,read() 是算法中抽象化的某个步骤,由子类。
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
Objects.checkFromIndexSize(off, len, b.length);
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;
}
}