Design Pattern - Template Method

模版方法模式

模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。——《Head First 设计模式(中文版)》

简单例子

冲泡咖啡和茶

咖啡冲泡步骤:

  1. 把水煮沸
  2. 用沸水冲泡咖啡
  3. 把咖啡倒进杯子
  4. 加糖和牛奶

茶冲泡步骤:

  1. 把水煮沸
  2. 用沸水浸泡茶叶
  3. 把咖啡倒进杯子
  4. 加柠檬

把共有的步骤及其顺序抽象为抽象类中的模版方法,可以共享的步骤放在抽象类中作为普通方法,需要子类实现的作为抽象方法。

抽象类 CaffeineBerverage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class CaffeineBeverage {

public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}

public abstract void brew();

public abstract void addCondiments();

public void boilWater() {
System.out.println("Boiling water");
}

public void pourInCup() {
System.out.println("Pouring into cup");
}
}

咖啡实现类

1
2
3
4
5
6
7
8
9
10
11
12
public class Coffee extends CaffeineBeverage {

@Override
public void brew() {
System.out.println("Steeping the coffee");
}

@Override
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}

茶实现类

1
2
3
4
5
6
7
8
9
10
11
12
public class Tea extends CaffeineBeverage {

@Override
public void brew() {
System.out.println("Steeping the tea");
}

@Override
public void addCondiments() {
System.out.println("Adding Lemon");
}
}

测试类

1
2
3
4
5
6
7
8
9
10
public class Test {

public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("--------------------");
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
}
}
1
2
3
4
5
6
7
8
9
Boiling water
Steeping the cffee
Pouring into cup
Adding Sugar and Milk
--------------------
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon

Java API 中的例子

java.io.InputStreamread() 方法,是抽象方法,需要由子类实现,而这个方法又会被 read(byte b[], int off, int len) 模版方法使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package java.io;

public abstract class InputStream implements Closeable {

public abstract int read() throws IOException;

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(); // 需要子类(例如 FileInputStream/ObjectInputStream)实现的方法!
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
1
2
3
4
5
6
7
8
9
package java.io;

public class FileInputStream extends InputStream {
public int read() throws IOException {
return read0();
}

private native int read0() throws IOException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package java.io;

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {

private final BlockDataInputStream bin;

public int read() throws IOException {
return bin.read();
}


private class BlockDataInputStream extends InputStream implements DataInput {
public int read() throws IOException {
if (blkmode) {
if (pos == end) {
refill();
}
return (end >= 0) ? (buf[pos++] & 0xFF) : -1;
} else {
return in.read();
}
}
}
}

Java EE API 中的例子

Servlet入门- 廖雪峰的官方网站

WEB 开发中常用 HttpServlet 里面的 service 方法处理请求,service 方法里面定义了调用流程,根据客户端调用的不同方式调用不同的方法,比如 doGetdoPostdoDelete 等实现 restful 调用,具体的 doGetdoPost 方法实现可以在自定义的 Servlet 中进行重写。

但需要注意的是 doGetdoPostdoDelete 等并没有被定义为抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package javax.servlet.http;

public abstract class HttpServlet extends GenericServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
}

Mybatis 中的例子

Mybatis 框架中的 BaseExecutor 类是一个基础的 SQL 执行类,实现了大部分 SQL 的执行逻辑,然后把几个方法交给子类定制化完成,例如模版方法 queryFromDatabase 中调用的 doQuery 方法就是一个抽象方法,需要子类提供实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.apache.ibatis.executor;

public abstract class BaseExecutor implements Executor {

protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); // 抽象方法需要子类提供实现
} finally {
this.localCache.removeObject(key);
}

this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}

return list;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.apache.ibatis.executor;

public class SimpleExecutor extends BaseExecutor {

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;

List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}

return var9;
}
}

优点

  1. 将相同处理逻辑的代码放到抽象父类中,把不变的行为写在父类中,去除了子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
  2. 将不同的逻辑放到不同的子类中,通过子类的扩展增加新的行为,提高了代码的扩展性。
  3. 通过父类调用子类的操作,通过对子类的扩展增加新的行为,实现了反向控制。

缺点

  1. 每个抽象类都需要至少一个子类来实现,导致了类数量的增加。
  2. 类数量增加间接增加了系统的复杂性。
  3. 因为继承关系的自身缺点,如果父类添加一个新的抽象方法,所有子类都要实现一遍。