设计模式八之模板方法模式(java)

版权声明:分享才能发挥最大的价值 https://blog.csdn.net/qq_32252957/article/details/80904910
这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。

我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。

我们将要深入封装算法块,好让子类可以在任何时候都可以讲自己挂接进算法里。另外我们会介绍一个新的面向对象设计原则"好莱坞原则".

我们举个栗子:

茶和咖啡的冲泡方式非常相似,下面是冲泡法:

咖啡冲泡法

(1) 把水煮沸

(2) 用沸水冲泡咖啡

(3) 把咖啡倒进杯子

(4) 加糖和牛奶

茶冲泡法

(1) 把水煮沸

(2) 用沸水浸泡茶叶

(3) 把茶倒进杯子

(4) 加柠檬

我们将写一些代码来创建咖啡和茶


直接看代码部分

咖啡类

public class Coffee {

	void prepareRecipe(){
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}

	public void boilWater(){
		System.out.println("Boiling water");
	}

	public void brewCoffeeGrinds(){
		System.out.println("Dripping Coffee through filter");
	}

	public void pourInCup(){
		System.out.println("Pouring into cup");
	}

	public void addSugarAndMilk(){
		System.out.println("Adding Sugar and Milk");
	}

}

茶类

public class Tea {

	void prepareRecipe(){
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}

	public void boilWater(){
		System.out.println("Boiling water");
	}

	public void steepTeaBag(){
		System.out.printn("Steeping the tea");
	}

	public void pourInCup(){
		System.out.println("Pouring into cup");
	}

	public void addLemon(){
		System.out.println("Adding Lemon");
	}
}

可以看到有一些重复的代码,这样我们可以将共同部分抽取出来,放进一个基类


但是,我们还忽略了咖啡和茶冲泡都是使用了相同的算法:

(1) 把水煮沸

(2) 用热水泡咖啡或茶

(3) 把饮料倒进杯子

(4) 在饮料内加入适当的饮料

我们重新将基类中的prepareRecipe()方法写成

void prepareRecipe(){
	boilWater();
	brew();
	pourInCup();
	addCondiments();
}

示例代码:

基类

public abstract class CaffeineBeverage {

	//prepareRecipe()被声明为final,我们不希望子类覆盖这个方法,因为这个是个算法模板
	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 Tea extends CaffeineBeverage {
	public void brew(){
		System.out.println("Steeping the tea");
	}

	public void addCondiments(){
		System.out.println("Adding Lemon");
	}
}

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

prepareRecipe()方法就是模板方法,定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。


下面是我们的模板方法模式出场了:

模板方法模式: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类

可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

类图如下:


模板方法的优点:

可以将代码的复用最大化,另外模板方法专注在算法本身,而由子类提供完整的实现


我们看一下抽象基类可以有哪些类型的方法:

abstract class AbstractClass {

	final void templateMethod(){
		primitiveOperation1();
		primitiveOperation2();
		concreteOperation();
		hook();
	}

	//这两个抽象方法由具体的子类实现
	abstract void primitiveOperation1();
	abstract void primitiveOperation2();

	//它可以被模板方法直接使用或者被子类使用
	final void concreteOperation(){
		//这里是实现
	}

	void hook(){
	//什么都不做,或者缺省默认一些操作
	}
}
hook()方法为"hook"(钩子),子类可以视情况决定要不要覆盖它们。


钩子的定义:

钩子是一种被声明在抽象类中的方法,但只有空或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。

钩子的用法:

1.钩子可以让子类实现算法中可选的部分,或者在钩子对子类的实现并不重要的时候,子类可以对此钩子置之不理。

2.让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤做出反应。

3.钩子可以让子类有能力为其抽象类作一些决定。


下面看示例代码:

基类

public abstract class CaffeineBeverageWithHook {

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

可以看到默认算法步骤是有addCondiments()方法也就是加调料环节的。

咖啡类

public class CoffeeWithHook extends CaffeineBeverageWithHook {

	public void brew(){
		System.out.println("Dripping coffe through filter");
	}

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

	//覆盖了钩子,提供了自己的功能
	public boolean customerWantsCondiments(){
		String answer = getUserInput();
	}

	private String getUserInput(){
		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 ioe){
			System.err.println("IO errir trying to read your answer");
		}
		if (answer == null){
			return '"no';
		}
		return answer;
	}
}
测试类
public class BeverageTestDrive {
	public static void main(String args[]){

		CoffeeWithHook coffeeHook = new CoffeeWithHook();

		System.out.println("\nMaking coffee...");
		coffeeHook.prepareRecipe();
	}
}

结果

Making coffee...
Boiling water
Dripping coffe through filter
Pouring into cup
Would you like milk and sugar with your coffee (y/n)? y
Adding Sugar and Milk
可以看到确实钩子可以作为条件控制,影响抽象类中的算法流程。


下面我们来介绍一个新的设计原则,好莱坞原则

好莱坞原则(Hollywood principle):

    Don't call me, I'll call you. 即别调用我们,我们会调用你。

高层组件对待低层组件的方式是:"别调用我们,我们会调用你",防止"依赖腐败"的方法。


高层组件是CaffeineBeverage类


在工厂模式那一节我们讲解了依赖倒置原则, 他和好莱坞原则比较相似

1.两者都是在于解耦,但是依赖倒置原则更加注重如何在设计中避免依赖。

2.依赖倒置原则教我们尽量避免使用具体类,而多使用抽象。而好莱坞原则是用在创建框架或组件上的一种技巧。


用模板方法来排序

Java数组类的设计者提供给我们一个便利的模板方法用来排序。

具体的实现代码请看API文档和源代码,这里给出一些核心的代码

这里有两个方法,共同提供排序的功能

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

private static void mergeSort(Object src[], Object dest[],
	int low, int high, int off){
	for(int i = low; i < high; i++){
		for(int j = i; j > low &&
			((Comparable)dest[j-1]).compareTo((Comparable)dest[j])>0; j--){
			swap(dest, j, j - 1); //swap已经在数组类中定义了 public static void swap(list<?> list, int i, intj)
		//在指定列表的指定位置处交换元素
		}
	}
}
我们接下来要比较鸭子

我们先要看Arrays类的静态方法sort方法

方法原型是 public static void sort(object[] a]

要注意的是: 根据元素的自然顺序对指定对象数组按升序进行排序。数组中的所有元素都必须实现Comparable

接口,此外数组中的所有元素都必须是可相互比较的。


所以我们需要让鸭子类实现Comparable接口,才能通过sort方法进行排序

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(int i = 0; i < ducks.length; i++){
			System.out.println(ducks[i]);
		}
	}
}

结果:

Before sorting:
Daffy weights 8
Dewey weights 2
Howard weights 7
Louie weights 2
Donald weights 10
Huey weights 2

After sorting:
Dewey weights 2
Louie weights 2
Huey weights 2
Howard weights 7
Daffy weights 8
Donald weights 10

sort()方法控制算法,没有类可以改变这一点,sort()依赖一个Comparable类提供compareTo()的实现


策略模式和模板方法都封装算法,一个用组合,一个用继承。工厂方法是模板方法的一个特殊版本

下次我们将介绍一个新的模式:迭代器和组合模式 我在以前学习C++、Python、Java语言的时候曾经多次用到迭代器,

但是始终不知道具体的实现原理和内容,下次我们将揭露迭代器到底是怎么工作的。



猜你喜欢

转载自blog.csdn.net/qq_32252957/article/details/80904910