HeadFirst设计模式(九) - 模板方法模式

直接举个例子

    来看看某咖啡厅里,茶和咖啡的冲泡方式。具体以下:java

    咖啡的冲泡法:算法

  1. 把水煮沸;
  2. 用沸水冲泡咖啡;
  3. 把咖啡倒进杯子;
  4. 加糖和牛奶;

    茶的冲泡法:ide

  1. 把水煮沸;
  2. 用沸水侵泡茶叶;
  3. 把茶倒进杯子;
  4. 加柠檬;

用代码实现上面的例子

public class Coffee {

	public Coffee() {
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void brewCoffeeGrinds() {
		System.out.println("用沸水冲泡咖啡...");
	}

	public void pourInCup() {
		System.out.println("把咖啡倒进杯子...");
	}

	public void addSugarAndMilk() {
		System.out.println("加糖和牛奶...");
	}
}
public class Tea {

	public Tea() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}
	
	public void boilWater() {
		System.out.println("把水煮沸...");
	}

	public void steepTeaBag() {
		System.out.println("用沸水侵泡茶叶...");
	}

	public void pourInCup() {
		System.out.println("把茶倒进杯子...");
	}

	public void addLemon() {
		System.out.println("加柠檬...");
	}
}

    上面两个类分别是咖啡类与茶类,咱们能够看到,两个类中有重复的代码,这表示咱们须要清理一下设计了。在这里,既然茶和咖啡师如此地类似,彷佛咱们应该将共同的部分抽取出来,放进一个基类中。函数

抽取相同的部分

    因为咖啡与茶都含有咖啡因,因此咱们抽象出一个咖啡因对象做为抽象基类,在制做咖啡与茶的过程当中,boilWater()方法和pourlnCup()方法是相同的,因此在基类中实现,其他的不一样函数则在子类中实现。测试

    在基类中,定义了一个抽象方法prepareRecipe(),它负责调用具体的制做步骤,由子类本身实现。url

    在上面这个设计中,还能够更进一步,在观察一下,咖啡与茶还有什么共同点呢?注意,两种饮品都采用了相同的冲泡方法:spa

  1. 把水煮沸;
  2. 用热水冲泡咖啡或者茶;
  3. 把饮品倒入杯子;
  4. 添加适当的调料;

    那么,咱们也将prepareRecipe()方法进行抽象,如今就来看看怎么作…….net

    将冲泡定义一个抽象方法brew(),在将添加调料定义一个抽象方法addCondiments(),最后将抽象方法prepareRecipe()进行修改,让他负责冲泡步骤,新的CaffeineBeverage类看起来就像这样:命令行

public abstract class CaffeineBeverage {
	
	// 相同的步骤在基类中作  
	public void boilWater() {
		System.out.println("把水煮沸...");
	}
	
	public void pourInCup() {
		System.out.println("把饮品倒进杯子...");
	}
	
	// 不一样的步骤由子类本身实现
	public abstract void brew();
	public abstract void addCondiments();
	
	// 基类负责对每一步进行调用
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}
}
package cn.net.bysoft.template;

public class Coffee extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水冲泡咖啡...");
	}
	@Override
	public void addCondiments() {
		System.out.println("加糖和牛奶...");
	}
}

package cn.net.bysoft.template;

public class Tea extends CaffeineBeverage {
	@Override
	public void brew() {
		System.out.println("用沸水侵泡茶叶...");
	}

	@Override
	public void addCondiments() {
		System.out.println("加柠檬...");
	}
}

    在上面的代码中,咱们所作的第一件事就是把原来brewCoffeeGrinds()方法和steepTeaBag()方法进行了抽象,接着把addSugarAndMilk()方法和addLemon()方法也进行了抽象。接下来将冲泡的步骤调用封装到了基类中的prepareRecipe()方法里,最后重写了Coffee对象和Tea对象。下面进行测试:设计

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 冲一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
	}
}

    测试成功,如今冲泡的步骤调用由CaffeineBeverage类主导,它拥有算法,并且保护这个算法。对子类来讲,CaffeineBeverage类的存在能够将代码的复用最大化。算法只存在于一个地方,因此容易修改。相同的算法有基类实现,不一样的算法由子类提供完整的实现。

定义模板方法模式

    这个方法将算法定义成一组步骤,其中的任何步骤均可以是抽象的,由子类负责实现。这能够确保算法的结构保持不变,同时由子类提供部分实现。

使用钩子

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

    根据上面的业务来举例,在是否添加调料时对客户进行询问,若是客户须要在添加调料。

    在上图中,基类加入了是否添加调料的钩子方法,在添加调料之间调用,算法仍是由prepareRecipe()方法管理,Coffee类重写了钩子函数,经过命令行向用户确认是否要添加调料,回复y/n。而茶类没有重写钩子函数,则调用缺省函数,调用ture,每次都添加调料。下面进行测试:

public class Client {
	public static void main(String[] args) {
		// 泡一杯茶
		Tea tea = new Tea();
		tea.prepareRecipe();
		
		System.out.println("\n");
		
		// 冲一杯咖啡
		Coffee coffee = new Coffee();
		coffee.prepareRecipe();
		
		System.out.println("\n");
		
		// 再冲一杯咖啡
		Coffee coffee2 = new Coffee();
		coffee2.prepareRecipe();
	}
}

好莱坞原则

别调用咱们,咱们会调用你。

    好莱坞原则能够给咱们一种防止“依赖腐败” 的方法。在好莱坞原则之下,咱们容许低层组件将本身挂钩到系统上,可是高层组件会决定何时和怎么使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用咱们,咱们会调用你”。

相关文章
相关标签/搜索