本文示例代码材料源自Head First设计模式
以前整理自己整理的链接:
https://blog.csdn.net/u011109881/article/details/60594985
简介
模板方法模式很容易理解。思想基本如下:先在父类规定了具体的算法步骤以及算法顺序。父类可以给出部分步骤的具体实现,也可以都只给出方法框架,没有具体实现。在子类具体实现各个步骤的方法,但是各个步骤间的顺序在父类已经确定,子类无法通常不应该更改。如果规定算法顺序的方法在父类被定义成final,则子类就无法更改了。具体实现,根据实际需求确定。
- 其目的一方面是减少代码重复,达到代码复用的目的
- 另一方面也可以在父类控制和限制子类的动作。
- 父类(泛化类)规定了一个算法框架,大多数时候,只要修改子类即可。
- 将具体算法和实现相分离,各司其职(单一职责),基类负责算法设计,(某些情况也会涉及一点实际实现),子类专司具体算法实现。
模板方法模式实例1
以泡茶和泡咖啡为例。他们是一个相近的例子。
泡茶的步骤如下:
- 把水煮沸
- 用沸水泡茶叶
- 把茶倒进杯子
- 加柠檬
而泡咖啡的步骤: - 把水煮沸
- 用沸水泡咖啡
- 把咖啡倒进杯子
- 加糖和牛奶
用代码很容易表示这段描述
public class Coffee {
public void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
private void addSugarAndMilk() {
System.out.println("addSugarAndMilk");
}
private void pourInCup() {
System.out.println("pourInCup");
}
private void brewCoffeeGrinds() {
System.out.println("brewCoffeeGrinds");
}
private void boilWater() {
System.out.println("boilWater");
}
}
public class Tea {
public void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
private void addLemon() {
System.out.println("addLemon");
}
private void steepTeaBag() {
System.out.println("steepTeaBag");
}
private void pourInCup() {
System.out.println("pourInCup");
}
private void boilWater() {
System.out.println("boilWater");
}
}
代码很简单,但是看上去似乎存在一些冗余代码,让我们做一下结构优化。
增加一个抽象父类,然后对coffee和tea进行一些改进。
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
//final修饰,子类无法改写
boilWater();
brew();
pourInCup();
addCondiments();
}
public abstract void brew() ;
public abstract void addCondiments() ;
private void pourInCup() {
System.out.println("pourInCup");
}
private void boilWater() {
System.out.println("boilWater");
}
}
public class Coffee extends CaffeineBeverage{
public void brew() {
System.out.println("brewCoffeeGrinds");
}
public void addCondiments() {
System.out.println("addSugarAndMilk ");
}
}
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("steepTeaBag");
}
public void addCondiments() {
System.out.println("addLemon");
}
}
public class Test {
public static void main(String[] args) {
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
测试结果:
boilWater
steepTeaBag
pourInCup
addLemon
boilWater
brewCoffeeGrinds
pourInCup
addSugarAndMilk
可以看到,CaffeineBeverage是coffee和tea的一个泛化。
关于模板方法的hook,实例1的修改版
模板方法中基类可以存在一些称之为hook的方法,这类方法在算法步骤中属于可选部分。他们的调用与否通常由子类决定。举个例子:
对基类稍作修改
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
//final修饰,子类无法改写
boilWater();
brew();
pourInCup();
if(customerWantsCondiments()){
addCondiments();
}else{
System.out.println("customer don't need condiments");
}
}
public abstract void brew() ;
public abstract void addCondiments() ;
private void pourInCup() {
System.out.println("pourInCup");
}
private void boilWater() {
System.out.println("boilWater");
}
boolean customerWantsCondiments(){
//该方法为hook方法,子类可以改写它,也可以不改。
return true;
}
}
对于实现类,可根据需要决定是否覆盖hook接口
比如Coffee类就修改了
public class Coffee extends CaffeineBeverage{
public void brew() {
System.out.println("brewCoffeeGrinds");
}
public void addCondiments() {
System.out.println("addSugarAndMilk ");
}
boolean customerWantsCondiments() {
String answer = getUserInput();
if(answer.startsWith("y")||answer.startsWith("Y")){
return true;
}else{
return false;
}
}
String getUserInput(){
System.out.println("do you want condiments?(y/n)");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String answer = null;
try {
answer = in.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return answer;
}
}
而Tea类则没有
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("steepTeaBag");
}
public void addCondiments() {
System.out.println("add condiment directly");
System.out.println("addLemon");
}
}
测试类则无需变动
public class Test {
public static void main(String[] args) {
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
System.out.println();
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
从测试结果可以看到,如果是tea则无需问是否添加调料,而在coffee时,却需要用户确认。
实际使用的模板方法很可能并不像上面的例子这样容易辨识。比如,如果我们有一个User的ArrayList,需要对User的age进行排序,那么可以用如下方式实现
大概框架:
ArrayList<User> list = new ArrayList();
list.sort(new Comparator<User>() {
public int compare(User o1, User o2) {
return 0;
}
});
这里其实用到的就是模板方法,很费解吧。
以下纯属个人理解,如有错误请指出:
ArrayList的排序是用Arrays的静态sort方法来实现的
这里的写法其实是实现了在Comparator中的hook接口:compare方法
sort中会使用两个comparable的对象进行compare,而实际compare时则调用的是我们上面实现的compare方法。
模板方法用的高深起来,也很难懂呢,所以其他有很多我们没有注意到的地方,都隐藏着设计模式的影子,等着我们发现呀。
Android种代码跟踪view的draw方法其实也是用的模板设计模式