Java设计模式之模板方法模式Template Method Pattern详解
流烟默 人气:0概述
模板方法
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。那么什么是模板方法呢?我们看下模板方法的定义。
- 一个具体方法而非抽象方法,其用作一个算法的模板;
- 在模板方法中,算法内的大多数步骤都被某个方法代表;
- 模板方法中某些方法是子类处理
- 需要由子类提供的方法,必须在超类中声明为抽象方法;
- 模板方法通常不能被覆盖,也就是使用final修饰;
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),指在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
这种类型的设计模式属于行为型模式。
如下实例,prepareRecipe就是一个模板方法。
public abstract class CaffeineBeverage { final void prepareRecipe(){ boilWater(); brew(); pourInCup(); addCondiments(); } protected abstract void addCondiments(); protected abstract void pourInCup(); protected abstract void brew(); protected abstract void boilWater(); }
模板方法模式
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
这个模式是用来创建一个算法的模板。什么是模板?如你所见的,模板就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。
对上面实例进一步扩展,我们看下抽象类内可以有哪些类型的方法。
public abstract class CaffeineBeverage { final void prepareRecipe(){ boilWater(); brew(); pourInCup(); addCondiments(); concreteOperation(); hook(); } protected abstract void addCondiments(); protected abstract void pourInCup(); protected abstract void brew(); protected abstract void boilWater(); final void concreteOperation(){ // 这里是实现 } //空方法 void hook(){}; }
可以看到有一个具体的concreteOperation方法,final表示其不可以被子类覆盖。我们也可以由“默认不做事的方法”,我们称这种方法为“hook”(钩子)。子类可以视情况决定要不要覆盖他们。
钩子是一种被声明在 抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。每一个具体的子类都必须定义所有的抽象方法,并为模板方法算法中未定义步骤提供完整的实现。
那么什么时候使用抽象方法什么时候使用钩子呢?
答,当你的子类必须提供算法中某个算法或步骤的实现时,就是用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现这个钩子,但并不强制这么做。
使用钩子的真正目的是什么?
钩子有几种用法。如我们之前所说的,钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。钩子的另一个用法,是让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。比方说,名为justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上重新显示数据)。正如前面提到的,钩子也可以让子类有能力为其抽象类作一些决定。
好莱坞原则
好莱坞原则简单来讲就是:别调用我们,我们会调用你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式就是“别调用我们,我们会调用你”。
模板方法模式就契合该原则。当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。
那么低层组件完全不可以调用高层组件的方法吗?并不尽然!
事实上,低层组件在结束时,常常调用从超类中继承来的方法。我们所要做的是,避免让高层和低层组件之间有明显的环状依赖。
好莱坞原则与依赖倒置原则
依赖倒置原则教我们尽量避免使用具体类,而多使用抽象类。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是在于解耦,但是依赖倒置原则更加注重如何在设计中避免依赖。
好莱坞原则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖它们。
真实案例
数组类Arrays的mergeSort方法就是一个模板方法。
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++]; } }
这里方法中,swap方法是一个具体方法。每一个元素需要实现compareTo方法,“填补”模板方法的缺憾。
可能会有疑问,上面这个案例真的是一个模板方法吗?
答,其符合模板方法的精神。这个模式的重点在于提供一个算法,并让子类实现某些步骤。而数组的排序做法很明显地并非如此!但是,我们都知道。Java api中的模式并非总是如同教科书例子一般地中规中矩,为了符合当前的环境和实现的约束,它们总是要被适当地修改。这个Arrays类sort方法的设计者受到一些约束。通常我们无法设计一个类继承Java数组,而sort()方法希望能够适用于所有的数组(每个数组都是不同的类)。所以它们定义了一个静态方法,而由被排序的对象内的每个元素自行提供比较大小的算法部分。所以,这虽然不是教科书上的模板方法,但它的实现仍然符合模板方法模式的精神。再者,由于不需要基础数组就可以使用这个方法,这样使得排序变得更有弹性、更有用。
另外一个案例是java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被read(byte b[], int off, int len)
模板方法使用。
模板方法模式的注意事项和细节
基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。一般模板方法都加上final 关键字, 防止子类重写模板方法。
模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理。
加载全部内容