设计模式学习(八) 模板方法模式

版权声明:有抱负的小狮子 https://blog.csdn.net/weixin_38087538/article/details/82791783

引入


定义:在一个方法中定义了一个算法的骨架,而将一些一些步骤延迟到子类中。模板方法使得子类可以在不改变算法接口的情况下,重新定义算法中的某些步骤。

uml类图

这个模式是用来创建一个算法的模板,什么是模板?如你所见的,模板就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中任何步骤都可以使抽象的,由子类负责实现,这样可以确保算法的结构保持不变,同时由子类提供部分实现。

示例

引用head first上的一个例子"咖啡冲泡手册"

咖啡冲泡法

1.把水煮沸

2.用沸水冲泡咖啡

3.把咖啡倒进杯子

4.加糖和牛奶

茶水冲泡法

1.把水煮沸

2.把沸水浸泡茶叶

3.把茶倒进杯子

4.加柠檬

我们可以看出两种冲泡方法,流程基本一致,我们将相同的行为提取出来,将类似的行为向上泛化,有子类完成具体的实现

抽象后:

1.把水煮沸

2.冲泡

3.倒进杯子

4.加料

package com.zpkj.project16;

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");
	}

}
package com.zpkj.project16;

public class Coffee extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Dripping Coffee through filter");
	}

	@Override
	void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}

}
package com.zpkj.project16;

public class Tea extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Steeping the tea");
		
	}

	@Override
	void addCondiments() {
		System.out.println("Adding lemon");
		
	}

}
package com.zpkj.project16;

public class Cilent {
	
	public static void main(String[] args) {
		CaffeineBeverage beverage1 = new Tea();
		CaffeineBeverage beverage2 = new Coffee();
		beverage1.prepareRecipe();
		System.out.println("-------------------");
		beverage2.prepareRecipe();
	}
	
}

结果

使用场景:

  多个子类有共有的方法,并且逻辑基本相同时; 重要、复杂的算法,可以把核心算法设计成模板方法,周边相关细节功能则由各个子类实现; 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

好处:

1.代码复用

2.保证算法结构不变,无需关心具体的实现

使用钩子

当子类必须提供算法中的实现时,就可以使用抽象方法,如果算法中的某个部分是 可选的,子类可以选择实现这个钩子。

改造上面的抽象模板代码

package com.zpkj.project16;

public abstract class CaffeineBeverage {
	
	final void prepareRecipe(){
		boilWater();
		brew();
		pourInCup();
		if(isCondiments()){
			addCondiments();			
		}
	}
	//将下层行为向上抽象
	abstract void brew();
	
	abstract void addCondiments();
	
	
	void boilWater(){
		System.out.println("Boiling water");
	}
	
	void pourInCup(){
		System.out.println("Pouring into cup");
	}
	
	boolean isCondiments(){
		return true;
	}

}

我们想要不加佐料的咖啡,在子类中覆盖钩子方法,

package com.zpkj.project16;

public class Coffee extends CaffeineBeverage{

	@Override
	void brew() {
		System.out.println("Dripping Coffee through filter");
	}

	@Override
	void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}

	@Override
	boolean isCondiments() {
		return false;
	}

}

结果

oo原则

1::好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。

好莱坞原则给我们一种防止“依赖腐败”的方法。当高层主键依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边界组件,边界组件又依赖底层组件时,系统设计就变的非常之复杂。

在好莱坞原则下,允许底层组件将自己挂钩到系统上,高层组件决定怎么使用这些底层组件!

"别调用我们,我们会调用你"

好莱坞原则和依赖倒置原则

1.依赖倒置教我们尽量避免使用具体类,而多实用抽象。

2.好莱坞原则是用在 创建框架或组件上的一种技巧,好让低层组件能够被挂钩到计算中,而不会让高层组件依赖底层组件。

两者的目标都在于解耦

jdk中的模板方法

在Arrays类中,提供了数组操作的排序方法。

 public static void sort(Object[] a) {
        Object[] aux = (Object[])a.clone();
        mergeSort(aux, a, 0, a.length, 0);
    }

mergeSort方法包含排序算法,此算法依赖于compartTo()方法的实现来完成算法

模板方法

 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++];
        }
    }

示例

package com.zpkj.array;

public class Duck implements Comparable<Duck>{
	
	String name;
	int weight;
	
	public Duck(String name,int weight) {
		super();
		this.name = name;
		this.weight = weight;
	}
	
	@Override
	public int compareTo(Duck o) {
		if(this.weight<o.weight){
			return -1;
		}else if(this.weight == o.weight){
			return 0;
		}else{
			return 1;
		}
	}

	@Override
	public String toString() {
		return "Duck [name=" + name + ", weight=" + weight + "]";
	}

}
package com.zpkj.array;

import java.util.Arrays;

public class Main {
	public static void main(String[] args) {
		Duck duck1 = new Duck("黄鸭子", 6);
		Duck duck2 = new Duck("绿鸭子", 3);
		Duck duck3 = new Duck("红鸭子", 8);
		Duck duck4 = new Duck("紫鸭子", 5);
		Duck[] ducks = new Duck[]{duck1,duck2,duck3,duck4};
		Arrays.sort(ducks);
		for(Duck e:ducks){
			System.out.println(e.toString());
		}
		
	}

}

结果

总结 

模板方法模式用四个字概括就是:流程封装。也就是把某个固定的流程封装到一个 final 函数中,并且让子类能够定制这个流程中的某些或者所有步骤,这就要求父类提取公用的代码,提升代码的复用率,同时也带来了更好的可封装性。

1.模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。

2.模板方法模式为我们提供了一种代码复用的重要技巧

3.模板方法的抽象类可以定义具体方法,抽象方法和钩子

4.抽象方法由子类实现

5.钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择是否覆盖它

6.为防止子类改变模板方法中的算法,可以将模板方法声明为final

7.好莱坞原则告诉我们。将决策权放在高层模板中,以便决定如何以及何时调用低层模板

8.策略模式和模板fang方法模式都封装算法,一个用组合,一个用继承

9.工厂方法是模板方法的一种特殊版本

源码下载 

https://github.com/isheroleon/design

猜你喜欢

转载自blog.csdn.net/weixin_38087538/article/details/82791783