首页 模板模式
文章
取消

模板模式

原理与实现

模板模式是我认为最简单的一种设计模式,因为学 java 的对继承一定再熟悉不过了吧,而模板模式就是基于继承,或者说就是继承的衍生用法。

定义

模板方法模式在一个方法中定义一个算法骨架(即模板),并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

简单实现

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
42
43
44
/**
 * 模板父类
 */
public abstract class TemplateClass {
  //模板方法被 final 修饰,不允许子类重写
  public final void templateMethod() {
    //...
    method1();
    //...
    method2();
    //...
  }
  
  //子类方法声明为 abstract 方法,由子类自定义实现
  protected abstract void method1();
  protected abstract void method2();
}


public class ConcreteClass1 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }
  
  @Override
  protected void method2() {
    //...
  }
}

public class ConcreteClass2 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }
  
  @Override
  protected void method2() {
    //...
  }
}
TemplateClass demo = ConcreteClass1();
demo.templateMethod();

当然这是模板模式最经典的写法,并不是说模板方法就一定非要使用 final 修饰,抽象方法也不是说非要用 abstract 给它修饰一下子类才能重写。实际上写法很灵活,重点能体现模板的复用效果即可。

JDK 内部由很多类使用到了模板方法,比如 InputStream 的 read() 这就是一个应用了模板模式的方法。

模板模式的另一个作用是扩展,其实再框架内部会经常使用到模板模式的,毕竟框架是业务当中不变的部分被抽出来单独成立,而变得部分就需要开发者自定义实现,这就正好是模板模式的拿手好戏。

就比如 Java Servlet,学习 Java 的时候一定再 web 开发的初期接触过它,使用它分两步,①继承 HttpServlet 并分别实现 doGet && doPost 方法。②在 xml 中配置 servlet <==> url 的映射。

而 HttpServlet 中的 service() 方法它就是一个应用了模板模式的方法。它的内部骨架化 web 流程,只留下 doPost && doGet 给开发者定制。

这就相当于Servlet框架提供了一个扩展点(doGet()、doPost()方法),让框架用户在不用修改Servlet框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。

模板模式与 Callback 回调函数

实际上 Callback 的实现方式与模板模式完全不同,那么 Callback 是什么,还记得 Java 的函数式编程吗,虽然说函数式是一种编程方式,但其实它的实现形式就是 Callback 这种,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,在 java 中就体现成了ICallback 匿名对象传递给了 b
    b.process(new ICallback() { //回调对象
      @Override
      public void methodToCallback() {
        System.out.println("Call back me.");
      }
    });
  }
}

像这样开发者可以自由实现回调函数并将它传递给其他对象,这样程序就具有了可扩展性和复用的功能,实际上不仅在代码设计上,在更高层次的架构设计上它也比较常用。比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。

回调函数又分为同步回调 && 异步回调,像上面的经典实现就是同步回调,因为调用者需要等到回调函数执行完毕,才能继续执行并返回执行结果。

回调函数的应用举例

  1. JdbcTemplate 的 query() 和 execute()
  2. 安卓客户端 setClickListener()
  3. Tomcat/JVM 的钩子函数(hook)

模板模式 VS 回调

  1. 应用场景几乎相同,基本在框架中用的比较多。
  2. 实现上回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

前面也学过组合由于继承,这里也不例外:回调相对模板模式会更加灵活,主要体现在下面这几个方面:

  1. 像Java这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
  2. 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
  3. 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。
本文由作者按照 CC BY 4.0 进行授权

享元模式

观察者模式