Notes
Search…
Template Method

概念

模板方法: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. 在一个操作中定义一个算法骨架,并将某些步骤推迟到子类实现。可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
算法可以理解为业务逻辑。
模板方法的作用:
  • 复用:子类都可以复用父类中模板方法定义的流程代码。如 InputStream、OutputStream、Reader、Writer,AbstractList 等。
  • 扩展:更多指框架的扩展性,类似控制反转,可以让用户不修改框架源码的情况下,定制化框架的功能。如 Java Servlet、JUnit 等。

示例

1
public abstract class AbstractClass {
2
public final void templateMethod() {
3
//...
4
method1();
5
//...
6
method2();
7
//...
8
}
9
10
protected abstract void method1();
11
protected abstract void method2();
12
}
13
14
public class ConcreteClass extends AbstractClass {
15
@Override
16
protected void method1() {}
17
18
@Override
19
protected void method2() {}
20
}
21
22
AbstractClass demo = ConcreteClass();
23
demo.templateMethod();
Copied!
模板方法用 final 修饰,避免子类重写;扩展点用 abstract 修饰,强迫子类实现。不过这两点都不是必须的。

源码

InputStream

1
2
public abstract class InputStream implements Closeable {
3
4
public int read(byte b[], int off, int len) throws IOException {
5
if (b == null) {
6
throw new NullPointerException();
7
} else if (off < 0 || len < 0 || len > b.length - off) {
8
throw new IndexOutOfBoundsException();
9
} else if (len == 0) {
10
return 0;
11
}
12
13
int c = read();
14
if (c == -1) {
15
return -1;
16
}
17
b[off] = (byte)c;
18
19
int i = 1;
20
try {
21
for (; i < len ; i++) {
22
c = read();
23
if (c == -1) {
24
break;
25
}
26
b[off + i] = (byte)c;
27
}
28
} catch (IOException ee) {
29
}
30
return i;
31
}
32
33
public abstract int read() throws IOException;
34
}
35
36
public class ByteArrayInputStream extends InputStream {
37
38
@Override
39
public synchronized int read() {
40
return (pos < count) ? (buf[pos++] & 0xff) : -1;
41
}
42
}
Copied!

AbstractList

1
public boolean addAll(int index, Collection<? extends E> c) {
2
rangeCheckForAdd(index);
3
boolean modified = false;
4
for (E e : c) {
5
add(index++, e);
6
modified = true;
7
}
8
return modified;
9
}
10
11
public void add(int index, E element) {
12
throw new UnsupportedOperationException();
13
}
Copied!

Servlet

用户实现的扩展点:
1
public class HelloServlet extends HttpServlet {
2
@Override
3
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
4
this.doPost(req, resp);
5
}
6
7
@Override
8
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
9
resp.getWriter().write("Hello World.");
10
}
11
}
Copied!
Servlet 容器收到请求后,会找到相应的 Servlet,并调用它的 service 方法。框架的模板方法:
1
public void service(ServletRequest req, ServletResponse res)
2
throws ServletException, IOException
3
{
4
HttpServletRequest request;
5
HttpServletResponse response;
6
if (!(req instanceof HttpServletRequest &&
7
res instanceof HttpServletResponse)) {
8
throw new ServletException("non-HTTP request or response");
9
}
10
request = (HttpServletRequest) req;
11
response = (HttpServletResponse) res;
12
service(request, response);
13
}
14
15
protected void service(HttpServletRequest req, HttpServletResponse resp)
16
throws ServletException, IOException
17
{
18
String method = req.getMethod();
19
if (method.equals(METHOD_GET)) {
20
long lastModified = getLastModified(req);
21
if (lastModified == -1) {
22
// servlet doesn't support if-modified-since, no reason
23
// to go through further expensive logic
24
doGet(req, resp);
25
} else {
26
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
27
if (ifModifiedSince < lastModified) {
28
// If the servlet mod time is later, call doGet()
29
// Round down to the nearest second for a proper compare
30
// A ifModifiedSince of -1 will always be less
31
maybeSetLastModified(resp, lastModified);
32
doGet(req, resp);
33
} else {
34
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
35
}
36
}
37
} else if (method.equals(METHOD_HEAD)) {
38
long lastModified = getLastModified(req);
39
maybeSetLastModified(resp, lastModified);
40
doHead(req, resp);
41
} else if (method.equals(METHOD_POST)) {
42
doPost(req, resp);
43
} else if (method.equals(METHOD_PUT)) {
44
doPut(req, resp);
45
} else if (method.equals(METHOD_DELETE)) {
46
doDelete(req, resp);
47
} else if (method.equals(METHOD_OPTIONS)) {
48
doOptions(req,resp);
49
} else if (method.equals(METHOD_TRACE)) {
50
doTrace(req,resp);
51
} else {
52
String errMsg = lStrings.getString("http.method_not_implemented");
53
Object[] errArgs = new Object[1];
54
errArgs[0] = method;
55
errMsg = MessageFormat.format(errMsg, errArgs);
56
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
57
}
58
}
Copied!

JUnit

JUnit 提供了一些扩展点,如 setUp(), tearDown() 等。
1
public abstract class TestCase extends Assert implements Test {
2
public void runBare() throws Throwable {
3
Throwable exception = null;
4
setUp();
5
try {
6
runTest();
7
} catch (Throwable running) {
8
exception = running;
9
} finally {
10
try {
11
tearDown();
12
} catch (Throwable tearingDown) {
13
if (exception == null) exception = tearingDown;
14
}
15
}
16
if (exception != null) throw exception;
17
}
18
19
/**
20
* Sets up the fixture, for example, open a network connection.
21
* This method is called before a test is executed.
22
*/
23
protected void setUp() throws Exception {
24
}
25
26
/**
27
* Tears down the fixture, for example, close a network connection.
28
* This method is called after a test is executed.
29
*/
30
protected void tearDown() throws Exception {
31
}
32
}
Copied!

Callback

回调与模板模式一样也具有复用和扩展的功能。

原理

A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数时,B 类反过来调用 A 类注册给 B 类的 F 函数,F 就叫做回调函数。
回调不仅可以用在代码设计上,还能用在高层次的架构设计上。比如用户向第三方支付系统发起支付,一般不会等到支付结果返回,而是注册回调函数(URL)给第三方支付系统,支付系统执行成功后,将结果通过回调接口返回给用户。
回调有同步回调和异步回调,同步回调更像模板模式,异步回调更像观察者模式。

示例

1
public interface ICallback {
2
void methodToCallback();
3
}
4
5
public class BClass {
6
public void process(ICallback callback) {
7
//...
8
callback.methodToCallback();
9
//...
10
}
11
}
12
13
public class AClass {
14
public static void main(String[] args) {
15
BClass b = new BClass();
16
b.process(new ICallback() { //回调对象
17
@Override
18
public void methodToCallback() {
19
System.out.println("Call back me.");
20
}
21
});
22
}
23
}
Copied!

JdbcTemplate

Spring 提供很多 Template 类,但它们并非基于模板方法实现,而是基于 Callback 实现。
如果直接写 JDBC 代码会很冗余,比如:
1
public class JdbcDemo {
2
public User queryUser(long id) {
3
Connection conn = null;
4
Statement stmt = null;
5
try {
6
//1.加载驱动
7
Class.forName("com.mysql.jdbc.Driver");
8
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "xzg", "xzg");
9
10
//2.创建statement类对象,用来执行SQL语句
11
stmt = conn.createStatement();
12
13
//3.ResultSet类,用来存放获取的结果集
14
String sql = "select * from user where id=" + id;
15
ResultSet resultSet = stmt.executeQuery(sql);
16
17
String eid = null, ename = null, price = null;
18
19
while (resultSet.next()) {
20
User user = new User();
21
user.setId(resultSet.getLong("id"));
22
user.setName(resultSet.getString("name"));
23
user.setTelephone(resultSet.getString("telephone"));
24
return user;
25
}
26
} catch (ClassNotFoundException e) {
27
// TODO: log...
28
} catch (SQLException e) {
29
// TODO: log...
30
} finally {
31
if (conn != null)
32
try {
33
conn.close();
34
} catch (SQLException e) {
35
// TODO: log...
36
}
37
if (stmt != null)
38
try {
39
stmt.close();
40
} catch (SQLException e) {
41
// TODO: log...
42
}
43
}
44
return null;
45
}
46
}
Copied!
加载驱动、创建连接、关闭连接等很多操作与业务无关,这些流程大多可以复用,所以 Spring 提供了 JdbcTemplate,用户代码可以变得非常简洁:
1
public class JdbcTemplateDemo {
2
private JdbcTemplate jdbcTemplate;
3
4
public User queryUser(long id) {
5
String sql = "select * from user where id="+id;
6
return jdbcTemplate.query(sql, new UserRowMapper()).get(0);
7
}
8
9
class UserRowMapper implements RowMapper<User> {
10
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
11
User user = new User();
12
user.setId(rs.getLong("id"));
13
user.setName(rs.getString("name"));
14
user.setTelephone(rs.getString("telephone"));
15
return user;
16
}
17
}
18
}
Copied!
JdbcTemplate 核心的逻辑如下:
1
@Override
2
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
3
return query(sql, new RowMapperResultSetExtractor<T>(rowMapper));
4
}
5
6
@Override
7
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
8
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
9
@Override
10
public T doInStatement(Statement stmt) throws SQLException {
11
ResultSet rs = null;
12
try {
13
rs = stmt.executeQuery(sql);
14
ResultSet rsToUse = rs;
15
if (nativeJdbcExtractor != null) {
16
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
17
}
18
return rse.extractData(rsToUse);
19
}
20
finally {
21
JdbcUtils.closeResultSet(rs);
22
}
23
}
24
@Override
25
public String getSql() {
26
return sql;
27
}
28
}
29
30
return execute(new QueryStatementCallback());
31
}
32
33
@Override
34
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
35
Assert.notNull(action, "Callback object must not be null");
36
37
Connection con = DataSourceUtils.getConnection(getDataSource());
38
Statement stmt = null;
39
try {
40
Connection conToUse = con;
41
if (this.nativeJdbcExtractor != null &&
42
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
43
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
44
}
45
stmt = conToUse.createStatement();
46
applyStatementSettings(stmt);
47
Statement stmtToUse = stmt;
48
if (this.nativeJdbcExtractor != null) {
49
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
50
}
51
T result = action.doInStatement(stmtToUse);
52
handleWarnings(stmt);
53
return result;
54
}
55
catch (SQLException ex) {
56
// Release Connection early, to avoid potential connection pool deadlock
57
// in the case when the exception translator hasn't been initialized yet.
58
JdbcUtils.closeStatement(stmt);
59
stmt = null;
60
DataSourceUtils.releaseConnection(con, getDataSource());
61
con = null;
62
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
63
}
64
finally {
65
JdbcUtils.closeStatement(stmt);
66
DataSourceUtils.releaseConnection(con, getDataSource());
67
}
68
}
Copied!

Hook

Callback 更加注重语法机制的描述,Hook 更加侧重应用的描述。Tomcat 和 JVM 都有shutdown hook,以 JVM 为例,当应用程序关闭时,会调用 ApplicationShutdownHooks 的 runHooks 方法:
1
public class ShutdownHookDemo {
2
3
private static class ShutdownHook extends Thread {
4
public void run() {
5
System.out.println("I am called during shutting down.");
6
}
7
}
8
9
public static void main(String[] args) {
10
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
11
}
12
}
13
14
15
public class Runtime {
16
public void addShutdownHook(Thread hook) {
17
SecurityManager sm = System.getSecurityManager();
18
if (sm != null) {
19
sm.checkPermission(new RuntimePermission("shutdownHooks"));
20
}
21
ApplicationShutdownHooks.add(hook);
22
}
23
}
24
25
class ApplicationShutdownHooks {
26
/* The set of registered hooks */
27
private static IdentityHashMap<Thread, Thread> hooks;
28
static {
29
hooks = new IdentityHashMap<>();
30
} catch (IllegalStateException e) {
31
hooks = null;
32
}
33
}
34
35
static synchronized void add(Thread hook) {
36
if(hooks == null)
37
throw new IllegalStateException("Shutdown in progress");
38
39
if (hook.isAlive())
40
throw new IllegalArgumentException("Hook already running");
41
42
if (hooks.containsKey(hook))
43
throw new IllegalArgumentException("Hook previously registered");
44
45
hooks.put(hook, hook);
46
}
47
48
static void runHooks() {
49
Collection<Thread> threads;
50
synchronized(ApplicationShutdownHooks.class) {
51
threads = hooks.keySet();
52
hooks = null;
53
}
54
55
for (Thread hook : threads) {
56
hook.start();
57
}
58
for (Thread hook : threads) {
59
while (true) {
60
try {
61
hook.join();
62
break;
63
} catch (InterruptedException ignored) {
64
}
65
}
66
}
67
}
68
}
Copied!

比较

同步回调几乎与模板模式一致,都是在一个大的算法骨架中,自由替换其中的个别步骤,起到代码复用和扩展的目的。
实现方式:模板方法基于继承;回调基于组合。
组合优于继承,那么回调相对于模板方法的优势如下:
  • Java 只支持单继承。
  • 回调可以使用匿名类,不用事先定义类。
  • 如果一个类有多个模板方法,每个类都有对应的抽象方法,那么模板方法需要实现所有的抽象方法;而回调只需要往我们需要的模板方法中注入回调对象即可。
Last modified 1yr ago