模板方法模式
场景描述:假设我们的咖啡馆拥有两种饮料的冲泡方法,但是它们的步骤是很类似的。因此我们想要将这种饮料冲泡法整合成一种模板式的方法,每种饮料仅针对其特定的调配模式自我实现。
我们目前设计如下
我们根据如上设计进行代码编写
首先在超类中定义模板方法
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
}
接着实现不同饮品的自定制方法
public class Coffee extends CaffeineBeverage {
public void brew(){
System.out.println("Dripping Coffee through filter");
}
public void addCondiments(){
System.out.println("Adding Sugar and Milk");
}
}
public class Tea extends CaffeineBeverage {
public void brew(){
System.out.println("Steeping the tea");
}
public void addCondiments(){
System.out.println("Adding Lemon");
}
}
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板就是一个方法,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。
其类图如下:
我们在使用模板方法时,可以通过设计一个默认不做事的方法,让子类视情况决定要不要覆盖它们。我们称其为钩子(hook)。
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
我们可以看一个实现钩子的示例
public abstract class CaffeineBeverageWithHook {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
boolean customerWantsCondiments(){ // 钩子
return true;
}
}
接着在特定子类中覆盖钩子方法
public class CoffeeWithHook extends CaffeineBeverageWithHook{
public void brew(){
System.out.println("Dripping Coffee through filter");
}
public void addCondiments(){
System.out.println("Adding Sugar and Milk");
}
public boolean customerWantsCondiments(){
String answer = null;
System.out.print("Would you like milk and sugar with your coffee (y/n)? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
}catch (IOException e){
System.err.println("IO error trying to read your answer");
}
if ("y".equals(answer)){
return true;
}else {
return false;
}
}
}
当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。
钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。
钩子的另一个用法是让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。
钩子也可以让子类有能力为其抽象类作一些决定。
好莱坞原则:别调用我们,我们会调用你。
高层组件对待低层组件的方式是“别调用我们,我们会调用你”。
当然凡事无绝对,我们尽可能要做的就是,避免让高层和低层组件之间有明显的环状依赖。
策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
策略模式定义一个算法家族,并让这些算法可以互换。正因为每个算法都被封装起来了,所以客户可以轻易地使用不同的算法。其通过对象组合的方式,让客户可以选择算法实现。
而模板方法模式定义一个算法的大纲,而由其的子类定义其中某些步骤的内容。
为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
总结:当我们的方法出现需要模板化的情况时,就可以考虑使用模板方法,但是现实世界中的模板方法并不是那么的直接,比如我们想要使用数组的sort排序功能。
在java8版本中
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);
}
而在mergeSort方法中存在部分代码如下
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;
}
故而当我们需要使用sort方法进行排序时,我们只需针对我们的对象实现compareTo方法即可。这也是一种模板方法模式的使用范例。
我们尝试做一下练习
public class Duck implements Comparable{
String name;
int weight;
public Duck(String name, int weight){
this.name = name;
this.weight = weight;
}
public String toString(){
return name + " weights " + weight;
}
public int compareTo(Object o){
Duck otherDuck = (Duck)o;
if (this.weight < otherDuck.weight){
return -1;
}else if (this.weight == otherDuck.weight){
return 0;
}else {
return 1;
}
}
}
接着我们测试一下
public class DuckSortTestDrive {
public static void main(String[] args) {
Duck[] ducks = {
new Duck("Daffy", 8),
new Duck("Dewey", 2),
new Duck("Howard", 7),
new Duck("Louie", 2),
new Duck("Donald", 10),
new Duck("Huey", 2)
};
System.out.println("Before sorting:");
display(ducks);
Arrays.sort(ducks);
System.out.println("\nAfter sorting:");
display(ducks);
}
public static void display(Duck[] ducks){
for (Duck duck : ducks){
System.out.println(duck);
}
}
}
结果如下
最后我们看一下这个练习中模板方法的工作原理