模板模式
主要是用来解决复用和扩展两个问题
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
模板模式作用一:复用
模板模式把一个算法中不变的流程抽象到父类的模板方法中,将可变的部分留给子类来实现。所有的子类都可以复用父类中模板方法定义的流程代码。
- Java InputStream
read(byte b[], int off, int len) 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了 read() 由子类来定制的抽象方法。
public abstract class InputStream implements Closeable {
//...省略其他代码...
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;
}
public class ByteArrayInputStream extends InputStream {
//...省略其他代码...
@Override
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
}
- Java AbstractList
addAll() 函数可以看作模板方法,add() 是子类需要重写的方法,尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportedOperationException 异常。前提是,如果子类不重写是不能使用的。
public abstract class AbstractList {
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
}
模板模式作用二:扩展
这里的扩展,并不是指代码的扩展性,而是指框架的扩展性。框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
-
Java Servlet
使用比较底层的 Servlet 来开发 Web 项目,只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。
Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的 Servlet ,然后执行它的 service() 方法。service() 方法定义在父类 HttpServlet 中,它会调用
doGet() 或 doPost() 方法,然后输出数据到网页。
HttpServlet 的 service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost() 是模板中可以由子类来定制的部分。
Servlet 框架提供了一个扩展点(doGet()、doPost() 方法),让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。 -
JUnit TestCase
JUnit 框架也通过模板模式提供了一些功能扩展点(setUp()、tearDown() 等),让框架用户可以在这些扩展点上扩展功能。
在使用 JUnit 测试框架来编写单元测试的时候,编写的测试类都要继承框架提供的 TestCase 类。在 TestCase 类中,runBare() 函数是模板方法,它定义了执行测试用例的整体流程:先执行 setUp()
做些准备工作,然后执行 runTest() 运行真正的测试代码,最后执行 tearDown() 做扫尾工作。
回调
相对于普通的函数调用,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是“回调函数”。A 调用 B,B 反过来又调用
A,这种调用机制就叫作“回调”。
- 同步回调指在函数返回之前执行回调函数;
public interface ICallback {
void methodToCallback();
}
public class BClass {
public void process(ICallback callback) {
//...
callback.methodToCallback();
//...
}
}
public class AClass {
public static void main(String[] args) {
BClass b = new BClass();
b.process(new ICallback() { //回调对象
@Override
public void methodToCallback() {
System.out.println("Call back me.");
}
});
}
}
-
异步回调指的是在函数返回之后执行回调函数
通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。
-
Spring JdbcTemplate
JdbcTemplate,对 JDBC 进一步封装,来简化数据库编程。使用 JdbcTemplate 查询用户信息,只需要编写跟这个业务有关的代码,其中包括,查询用户的 SQL 语句、查询结果与 User
对象之间的映射关系。其他流程性质的代码(加载驱动、创建数据库连接、创建 statement、关闭连接、关闭 statement、处理异常)都封装在了 JdbcTemplate 类中,不需要我们每次都重新编写。
public class JdbcTemplateDemo {
private JdbcTemplate jdbcTemplate;
public User queryUser(long id) {
String sql = "select * from user where id=" + id;
return jdbcTemplate.query(sql, new UserRowMapper()).get(0);
}
class UserRowMapper implements RowMapper<User> {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setTelephone(rs.getString("telephone"));
return user;
}
}
}
// JdbcTemplate 通过回调的机制,将不变的执行流程抽离出来,放到模板方法 execute() 中,将可变的部分设计成回调 StatementCallback,由用户来定制。query() 函数是对 execute() 函数的二次封装,让接口用起来更加方便。
模板模式 VS 回调
- 应用场景
同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。
异步回调跟模板模式有较大差别,更像是观察者模式。 - 代码实现
回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系
模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。 - 组合优于继承!在代码实现上,回调相对于模板模式会更加灵活
1、 Java 只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
2、回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
3、如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。