
概念感受——模板
模板,原义是指带有镂空文字的薄薄塑料板。只要用笔在模板的镂空处进行临摹,即使是手写也能写出整齐的文字。虽然只要看到这些镂空的洞,我们就可以知道能写出哪些文字,但是具体写出的文字是什么感觉则依赖于所用的笔。如果使用签字笔来临摹,则可以写出签字似的文字;如果使用铅笔来临摹,则可以写出铅笔字;而如果是用彩色笔临摹,则可以写出彩色的字。但是无论使用什么笔,文字的形状都会与模板上镂空处的形状一致。
模板方法设计模式 Template Method Design Pattern
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.——GoF
在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里的「算法」可理解为广义上的「业务逻辑」。模板方法模式在父类中定义处理流程的框架,允许子类中实现每个流程的具体处理。
登场角色:
AbstractClass(抽象类):实现模板方法,声明在模板方法中所使用到的抽象方法。这些方法由子类 ConcreteClass 角色负责实现。ConcreteClass(具体类):实现 AbstractClass 角色中定义的抽象方法。这里的实现方法会在 AbstractClass 角色得模板方法中被调用。
在模板模式经典的实现中,模板方法定义为
final,可以避免被子类重写。需要子类重写的方法定义为abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。
观察上面的 AbstractClass,其中 templateMethod 为模板方法,里面使用了 method1、method2、method3 抽象方法。这些抽象方法需要子类去实现。
代码示例:
1 | public class TemplateTest { |
类的层次与抽象类:
拓展思路要点:
instanceof 等指定子类的种类,程序也能正常工作。LSP 是通用的继承原则,并非仅限于模板方法模式。两大作用:
应用场景:
| 优点 | 缺点 |
|---|---|
| 可仅允许客户端重写一个大型算法中的特定部分,使得算法其他部分修改对其所造成的影响减小。 | 部分客户端可能会受到算法框架的限制。 |
| 可将重复代码提取到一个超类中。 | 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。 |
| 模板方法中的步骤越多,其维护工作就可能会越困难。 |

站内文章工厂(方法)模式 是将模板方法模式用于生成实例的一个典型例子。
与策略模式的对比:
| 设计模式 | 模板方法模式 | 策略模式 |
|---|---|---|
| 改变程序行为的技术 | 继承。在父类中定义程序行为的框架,在子类中决定具体的处理。 | 委托。 |
| 关注点 | 算法的整体框架、步骤。 | 不同的算法实现。将算法的实现与具体的类解耦。 |
| 改变程序行为 | 子类可以改变部分程序行为。 | 替换整个算法。 |
InputStream、OutputStream、Reader、Writer 类都使用了模板方法模式。AbstractList 的 addAll() 函数也使用了模板方法模式。
Java 中的 Servlet 生命周期就是使用了模板方法模式,其中抽象类 HttpServlet 定义了 doGet、doPost 等操作方法,而具体的 Servlet 类可以继承 HttpServlet 并重写具体的操作方法来实现自定义的业务逻辑。
相对于普通的函数调用来说,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是「回调函数」。A 调用 B,B 反过来又调用 A,这种调用机制就叫作「回调」。
对于回调,不同的编程语言有不同的实现方式,C 语言中使用函数指针来实现。
回调可以分为同步回调和异步回调(或者延迟回调)。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。
Java 中同步回调函数的典型实现:
1 | public interface ICallback { |
同步回调更像是模板方法模式:将不变的执行流程抽离出来,放到模板方法中,然后将可变的部分实际成回调,由用户来定制。
比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。
从应用场景上来看,同步回调看起来更像模板模式,它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。异步回调看起来更像观察者模式。
从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
组合优于继承。在代码实现上,回调相对于模板模式会更加灵活,主要体现在:
Callback 是一种异步调用的实现,Callback 本意就是你传递一个函数给对方,当对方的工作有进展的时候就调用这个函数通知你。
Hook 则是一种 API 拦截手段,特点就是不改变原有双边的逻辑的情况下,在 API 接口上插入一个拦截调用的 Hook 函数,从而截取调用数据、甚至可以改变程序行为。
Java 8 中最大的新特性就是提供了对 站内文章函数式编程 的支持。Java 8 在 java.util.function 下面增加增加一系列的函数接口。其中主要有 Consumer、Supplier、Predicate、Function 等。
在上文中,我们可以知道模板方法模式中,子类通过实现抽象方法替换父类算法框架中的某个步骤。但是如果父类的模板方法中,一些步骤可以进行多种替换。如果仅靠子类实现将会需要编写许多的类。
我们可以进行以下改进:
文章开头的案例可以改造为:
1 | public class TemplateTest { |
长得很像上一节讲的同步回调。它的缺点是理解成本增高。
1 | abstract class AbstractClass{ |

概念感受——模板
模板,原义是指带有镂空文字的薄薄塑料板。只要用笔在模板的镂空处进行临摹,即使是手写也能写出整齐的文字。虽然只要看到这些镂空的洞,我们就可以知道能写出哪些文字,但是具体写出的文字是什么感觉则依赖于所用的笔。如果使用签字笔来临摹,则可以写出签字似的文字;如果使用铅笔来临摹,则可以写出铅笔字;而如果是用彩色笔临摹,则可以写出彩色的字。但是无论使用什么笔,文字的形状都会与模板上镂空处的形状一致。
模板方法设计模式 Template Method Design Pattern
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.——GoF
在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里的「算法」可理解为广义上的「业务逻辑」。模板方法模式在父类中定义处理流程的框架,允许子类中实现每个流程的具体处理。
登场角色:
AbstractClass(抽象类):实现模板方法,声明在模板方法中所使用到的抽象方法。这些方法由子类 ConcreteClass 角色负责实现。ConcreteClass(具体类):实现 AbstractClass 角色中定义的抽象方法。这里的实现方法会在 AbstractClass 角色得模板方法中被调用。
在模板模式经典的实现中,模板方法定义为
final,可以避免被子类重写。需要子类重写的方法定义为abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。
观察上面的 AbstractClass,其中 templateMethod 为模板方法,里面使用了 method1、method2、method3 抽象方法。这些抽象方法需要子类去实现。
代码示例:
1 | public class TemplateTest { |
类的层次与抽象类:
拓展思路要点:
instanceof 等指定子类的种类,程序也能正常工作。LSP 是通用的继承原则,并非仅限于模板方法模式。两大作用:
应用场景:
| 优点 | 缺点 |
|---|---|
| 可仅允许客户端重写一个大型算法中的特定部分,使得算法其他部分修改对其所造成的影响减小。 | 部分客户端可能会受到算法框架的限制。 |
| 可将重复代码提取到一个超类中。 | 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。 |
| 模板方法中的步骤越多,其维护工作就可能会越困难。 |

站内文章工厂(方法)模式 是将模板方法模式用于生成实例的一个典型例子。
与策略模式的对比:
| 设计模式 | 模板方法模式 | 策略模式 |
|---|---|---|
| 改变程序行为的技术 | 继承。在父类中定义程序行为的框架,在子类中决定具体的处理。 | 委托。 |
| 关注点 | 算法的整体框架、步骤。 | 不同的算法实现。将算法的实现与具体的类解耦。 |
| 改变程序行为 | 子类可以改变部分程序行为。 | 替换整个算法。 |
InputStream、OutputStream、Reader、Writer 类都使用了模板方法模式。AbstractList 的 addAll() 函数也使用了模板方法模式。
Java 中的 Servlet 生命周期就是使用了模板方法模式,其中抽象类 HttpServlet 定义了 doGet、doPost 等操作方法,而具体的 Servlet 类可以继承 HttpServlet 并重写具体的操作方法来实现自定义的业务逻辑。
相对于普通的函数调用来说,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是「回调函数」。A 调用 B,B 反过来又调用 A,这种调用机制就叫作「回调」。
对于回调,不同的编程语言有不同的实现方式,C 语言中使用函数指针来实现。
回调可以分为同步回调和异步回调(或者延迟回调)。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。
Java 中同步回调函数的典型实现:
1 | public interface ICallback { |
同步回调更像是模板方法模式:将不变的执行流程抽离出来,放到模板方法中,然后将可变的部分实际成回调,由用户来定制。
比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。
从应用场景上来看,同步回调看起来更像模板模式,它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。异步回调看起来更像观察者模式。
从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
组合优于继承。在代码实现上,回调相对于模板模式会更加灵活,主要体现在:
Callback 是一种异步调用的实现,Callback 本意就是你传递一个函数给对方,当对方的工作有进展的时候就调用这个函数通知你。
Hook 则是一种 API 拦截手段,特点就是不改变原有双边的逻辑的情况下,在 API 接口上插入一个拦截调用的 Hook 函数,从而截取调用数据、甚至可以改变程序行为。
Java 8 中最大的新特性就是提供了对 站内文章函数式编程 的支持。Java 8 在 java.util.function 下面增加增加一系列的函数接口。其中主要有 Consumer、Supplier、Predicate、Function 等。
在上文中,我们可以知道模板方法模式中,子类通过实现抽象方法替换父类算法框架中的某个步骤。但是如果父类的模板方法中,一些步骤可以进行多种替换。如果仅靠子类实现将会需要编写许多的类。
我们可以进行以下改进:
文章开头的案例可以改造为:
1 | public class TemplateTest { |
长得很像上一节讲的同步回调。它的缺点是理解成本增高。
1 | abstract class AbstractClass{ |