Head First设计模式读书笔记七 第八章 模板方法模式

本文示例代码材料源自Head First设计模式
以前整理自己整理的链接:
https://blog.csdn.net/u011109881/article/details/60594985

简介

模板方法模式很容易理解。思想基本如下:先在父类规定了具体的算法步骤以及算法顺序。父类可以给出部分步骤的具体实现,也可以都只给出方法框架,没有具体实现。在子类具体实现各个步骤的方法,但是各个步骤间的顺序在父类已经确定,子类无法通常不应该更改。如果规定算法顺序的方法在父类被定义成final,则子类就无法更改了。具体实现,根据实际需求确定。

  • 其目的一方面是减少代码重复,达到代码复用的目的
  • 另一方面也可以在父类控制和限制子类的动作。
  • 父类(泛化类)规定了一个算法框架,大多数时候,只要修改子类即可。
  • 将具体算法和实现相分离,各司其职(单一职责),基类负责算法设计,(某些情况也会涉及一点实际实现),子类专司具体算法实现。

模板方法模式实例1

以泡茶和泡咖啡为例。他们是一个相近的例子。
泡茶的步骤如下:

  1. 把水煮沸
  2. 用沸水泡茶叶
  3. 把茶倒进杯子
  4. 加柠檬
    而泡咖啡的步骤:
  5. 把水煮沸
  6. 用沸水泡咖啡
  7. 把咖啡倒进杯子
  8. 加糖和牛奶
    用代码很容易表示这段描述
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方法其实也是用的模板设计模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值