模板方法模式定义了一个算法 https://baike.baidu.com/item/算法 的步骤, 并允许子类别为一个或多个步骤提供其实践方式. 让子类别在不改变算法架构的情况下, 重新定义算法中的某些步骤.(来自百度百科)
模板方法模式在框架中经常使用, 学习此模式后, 对于阅读源码能力会有很大的提升. 我准备先描述生活中的实际场景, 引申出模板方式模式, 然后分析此模式在 JDK 中的使用, 最后分析在框架 SpringMVC 中的使用.
1, 冲咖啡和泡奶茶如何抽取成一个模板
1.1 模板思路
冲咖啡步骤
(1)把水煮沸
(2)把沸水冲咖啡
(3)倒进杯子里
(4)加奶加糖
泡奶茶步骤
(1)把水煮沸
(2)把沸水泡茶叶
(3)倒进杯子里
(4)加点奶
可以发现, 冲咖啡和泡奶茶有两个步骤是一样的: 把水煮沸和倒入杯子中, 有两个步骤不一样. 那么是不是将两个相同的方法抽取在父类当中, 用 final 修饰, 让咖啡和奶茶两个子类去继承这个父类, 两个子类就具有了父类把水煮沸和倒入杯子中的能力. 再去分别实现另外两个步骤就可以了, 最后完成冲咖啡和泡奶茶的流程.
可是, 这种做法对于父类来说, 整个步骤是不可控的, 虽然子类具有了父类的方法, 但是采用了怎样的步骤顺序去制作咖啡和奶茶, 父类完全不清楚. 所以, 这里要做出一些改进, 如果我们能在父类中将咖啡或者奶茶的每一个步骤都定义好, 需要子类实现的方法定义成抽象类, 然后将整个流程封装在一个方法里, 子类继承父类后直接调用父类的这个方法, 就能完全控制住整个操作步骤了.
1.2 具体的代码实现
- /**
- * 茶和咖啡的抽象父类
- */
- public abstract class CaffeineBeverage {
- public final void prepareRecipe(){
- boilWater();
- brew();
- pourCup();
- addCondiment();
- }
- abstract void brew();
- abstract void addCondiment();
- void boilWater(){
- System.out.println("烧水");
- }
- final void pourCup(){
- System.out.println("倒入杯子中");
- }
- }
- public class Coffee extends CaffeineBeverage {
- @Override
- void brew() {
- System.out.println("加入咖啡");
- }
- @Override
- void addCondiment() {
- System.out.println("加入奶泡和糖");
- }
- }
- public class Tea extends CaffeineBeverage {
- @Override
- void brew() {
- System.out.println("放入茶叶");
- }
- @Override
- void addCondiment() {
- System.out.println("加点奶");
- }
- }
- public class TemplateMethodPattern {
- public static void main(String[] args) {
- Coffee coffee = new Coffee();
- coffee.prepareRecipe();
- }
- }
1.3 代码分析
使用了模板方法, 子类只需要去继承父类并实现父类中的抽象方法, 但具体的执行步骤还是在父类 CaffeineBeverage 中定义并用 final 修饰的, 这一点保证了步骤的统一. 另外, 在父类中具体的方法并不一定要被 final 修饰, 可以由子类去决定用不用或怎么使用. 重点的是模板方法的思想, 父类控制流程, 子类控制某些步骤.
另外, 还有一点, 创建模板对象时, 要不要用模板对象去接收? 这个问题我的想法是可以, 看具体的使用场景.
2, Java API 中的模板方法思想
2.1 数组的排序
平时我们对操作数据排序时, 会使用 Arrays.sort(Object[] o)方法. sort()调用的最重要的方法是 mergeSort(), 他就是一个模板方法, 依赖实现类实现 comparaTo()完成排序. sort()方法和 mergeSort()方法这些都是静态方法之间的调用, 在比较数据中的对象时, 会先将对象强转成 Comparable, 调用 CompareTo()进行比较, 如果需要交换位置, 则调用 swap(). 这也就是为什么, 当我们自定义类排序时, 一定要实现 Compareble 并重写 compareTo().
- public class Arrays {
- ...
- public static void sort(Object[] a) {
- if (LegacyMergeSort.userRequested)
- legacyMergeSort(a);
- else
- ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
- }
- private static void legacyMergeSort(Object[] a) {
- Object[] aux = a.clone();
- mergeSort(aux, a, 0, a.length, 0);
- }
- // 排序中的模板方法, 依赖 comparaTo()方法的实现完成
- private static void mergeSort(Object[] src,
- Object[] dest,
- int low,
- int high,
- int off) {
- int length = high - low;
- // Insertion sort on smallest arrays
- if (length <INSERTIONSORT_THRESHOLD) {
- for (int i=low; i<high; i++)
- for (int j=i; j>low &&
- ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
- swap(dest, j, j-1);
- return;
- }
- // Recursively sort halves of dest into src
- int destLow = low;
- int destHigh = high;
- low += off;
- high += off;
- int mid = (low + high)>>> 1;
- mergeSort(dest, src, low, mid, -off);
- mergeSort(dest, src, mid, high, -off);
- // If list is already sorted, just copy from src to dest. This is an
- // optimization that results in faster sorts for nearly ordered lists.
- if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
- System.arraycopy(src, low, dest, destLow, length);
- return;
- }
- // Merge sorted halves (now in src) into dest
- for(int i = destLow, p = low, q = mid; i <destHigh; i++) {
- if (q>= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
- dest[i] = src[p++];
- else
- dest[i] = src[q++];
- }
- }
- ...
- }
2.2 代码分析
看到这里, 可能会有些迷惑, 这里为什么没有用继承, 模板方法的核心不应该是继承吗? 其实并不是这样, 只要能让流程在类中控制住, 子类或实现类实现需要实现的部分, 这就是一个完整的模板方法模式. sort 方法是静态方法, 就可以在所有数组中使用, 只需要这个数组中的对象实现 Comparable, 这样用起来更加方便, 假如是继承的话, 是没法保证每一个类都能继承 Comparable, 毕竟 Java 只支持单继承.
3,SpringMVC 中的模板方法
未完待续...
来源: http://www.bubuko.com/infodetail-2869320.html