行为型-模板模式
# 模板模式
- Template Method Design Pattern
- 模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。
- 模板方法可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
- 在 Java 中定义这样的模板方法,为了避免子类重写它,需要将函数定义为 final。
# 模板方法作用
- 复用
- 模板模式把一个算法中不变的流程抽象到父类的模板方法中,将可变的部分留给子类来实现。
- 所有的子类都可以复用父类中的模板方法定义的流程代码。
- 主要通过继承父类代码来实现。
- 实例
- Java InputStream
- 在 InputStream 读取数据中,有个 read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。
- Java AbstractList
- 在 AbstractList 类中,addAll() 函数可以看做模板方法,add() 是子类需要重写的方法。
- 尽管没有声明为 abstract 的,但函数实现直接抛出了 UnsupportOperationException 异常。
- Java InputStream
- 扩展
- 这里的扩展,指的不是代码的扩展性,而是框架的扩展性,有点类似之前讲到的控制反转。
- 实例
- Java Servlet
- 使用 Servlet 开发 Web 项目,我们需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get 和 post 请求。
- HttpServlet 的 service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost() 是模板中可以由子类来定制的部分。
- Servlet 框架提供了一个扩展点(doGet()、doPost()方法),让框架用户在不用改变 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
- Junit TestCase
- 在使用 JUnit 测试框架来编写单元测试的时候,我们编写的测试类都要继承框架提供的 TestCase 类。
- 在 TestCase 类中,runBare() 函数是模板方法,它定义了执行测试用例的整体流程:先执行 setUp() 做些准备工作,然后执行 runTest() 运行真正的测试代码,最后执行 tearDown() 做扫尾工作。
- TestCase 类的具体代码如下所示。尽管 setUp()、tearDown() 并不是抽象函数,还提供了默认的实现,不强制子类去重新实现,但这部分也是可以在子类中定制的,所以也符合模板模式的定义。
- Java Servlet
# 回调函数
# 定义
- 相对于普通的函数调用来说,回调是一种双向调用关系。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.");
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 回调分类
- 同步回调
- 在函数返回之前执行回调函数。
- 异步回调
- 类似支付时,调用第三方接口,支持同步回调和异步回调。
# 应用实例
- JdbcTemplate
- 尽管都叫作 xxxTemplate,但它们并非基于模板模式来实现的,而是基于回调来实现的,确切地说应该是同步回调。
- Java 提供了 JDBC 类库来封装不同类型的数据库操作。不过,直接使用 JDBC 来编写操作数据库的代码,还是有点复杂的。
- Spring 提供了 JdbcTemplate,对 JDBC 进一步封装,来简化数据库编程。
- JdbcTemplate 通过回调的机制,将不变的执行流程抽离出来,放到模板方法 execute() 中,将可变的部分设计成回调 StatementCallback,由用户来定制。
- query() 函数是对 execute() 函数的二次封装,让接口用起来更加方便。
- Android setClickListener
- 事件监听器很像回调,即传递一个包含回调函数(onClick())的对象给另一个函数。
- 当用户点击按钮的时候,发送点击事件给观察者,并且执行相应的 onClick() 函数。
- addShutdownHook
- JVM 提供了 Runtime.addShutdownHook(Thread hook) 方法,可以注册一个 JVM 关闭的 Hook。当应用程序关闭的时候,JVM 会自动调用 Hook 代码。
- 有关 Hook 的逻辑都被封装到 ApplicationShutdownHooks 类中了。当应用程序关闭的时候,JVM 会调用这个类的 runHooks() 方法,创建多个线程,并发地执行多个 Hook。
# 模板模式 VS 回调
- 从应用场景
- 同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。
- 而异步回调跟模板模式有较大差别,更像是观察者模式。
- 从代码实现
- 回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;
- 模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
# 回调相对于模板方法
- 像 Java 这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
- 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
- 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。
上次更新: 2020/10/18, 14:10:00