您的位置:首页 > 其它

设计模式之模板方法模式

2016-02-11 15:07 344 查看
模板方法模式:由子类决定如何实现父类算法中的哪一步。

例子:咖啡机自动冲咖啡,但是我们可能做卡布奇诺(Cappuccino),也可能做焦糖玛奇朵(CaramelMacchiato),但是总体来说步骤一致,只是最后加入的其他材料不太一样。

冲咖啡的步骤:获得咖啡豆(getBeans()),放入咖啡机(putIntoCoffeemaker()),磨碎咖啡豆(grindBeans()),冲咖啡(makeCoffee())。

假设有这么一个抽象类,AutoCoffee,它的代码如下:

public abstract class AutoCoffee {
	
	//获得咖啡豆
	protected void getBeans(){
		System.out.println("获得咖啡豆");
	}
	
	//放入咖啡机
	protected void putIntoCoffeemaker(){
		System.out.println("放入咖啡机");
	}
	
	//磨碎咖啡豆
	protected void grindBeans(){
		System.out.println("磨碎咖啡豆");
	}
	
	//冲咖啡
	protected void makeCoffee(){
		System.out.println("冲咖啡");
	}
	
	//自动冲咖啡
	final public void autoMake(){
		this.getBeans();
		this.putIntoCoffeemaker();
		this.grindBeans();
		this.makeCoffee();
	}
}


客户端不用关心自动冲咖啡内部的步骤如何实现,只需要调用autoMake()方法就够了,既然这样,我们的步骤方法,都设为protected。

然后,我们需要一个设定,让咖啡机自动冲卡布奇诺,只需要在最后加入牛奶和奶泡就好了:

Cappuccino.java

public class Cappuccino extends AutoCoffee {
	
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和奶泡");
	}
}
再来一个设定,自动冲焦糖玛奇朵,只需要在最后加入牛奶和焦糖:

CaramelMacchiato.java

public class CaramelMacchiato extends AutoCoffee {

	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和焦糖");
	}
	
}
OK,这样,来一个客户端:

Clinet.java

public class Clinet {
	public static void main(String[] args) {
			AutoCoffee cappuccino = new Cappuccino();
			AutoCoffee caramelMacchiato = new CaramelMacchiato();
			cappuccino.autoMake();
			caramelMacchiato.autoMake();
	}
}


输出结果:

获得咖啡豆

放入咖啡机

磨碎咖啡豆

冲咖啡

加入牛奶和奶泡

---------------------

获得咖啡豆

放入咖啡机

磨碎咖啡豆

冲咖啡

加入牛奶和焦糖

以上就是模板方法。是的,就是这么简单,或许你有疑问:如果子类需要更改冲咖啡的过程,或者需要稍微不同怎么办?

针对第一种情况,如果真的很不一样,需要大改,几近完全不同,那么,你不应该继承AutoCoffee这个抽象类,而是写一个类。但是如果只是稍微不同,那么我们就引用钩子(hook)的概念。

假设,我们的磨碎咖啡豆这一步骤,有手摇和电动两种选择,无论是卡布奇诺还是焦糖玛奇朵,我们都采用手动,但是实现方式不一样,我们可以这样编写抽象类AutoCoffee

public abstract class AutoCoffee {
	private boolean flag = true;	
	//获得咖啡豆
	protected void getBeans(){
		System.out.println("获得咖啡豆");
	}
	//放入咖啡机
	protected void putIntoCoffeemaker(){
		System.out.println("放入咖啡机");
	}
	//钩子方法
	protected boolean isMotorDriven(){
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}	
	//磨碎咖啡豆
	protected void grindBeansByMotorDriven(){
		System.out.println("电动磨碎咖啡豆");
	}
	protected void grindBeansByHand(){
		System.out.println("手动磨碎咖啡豆");
	}	
	//冲咖啡
	protected void makeCoffee(){
		System.out.println("冲咖啡");
	}	
	//自动冲咖啡
	final public void autoMake(){
		this.getBeans();
		this.putIntoCoffeemaker();
		
		if(isMotorDriven()){
			this.grindBeansByMotorDriven();
		}else{
			this.grindBeansByHand();
		}
		this.makeCoffee();
	}
}


新增加一个属性:flag和setter方法,以及判断flag的isMotorDriven()方法,grindBeans()变为两个方法,一个是grindBeansByMotorDriven()电动磨碎咖啡豆,一个是grindBeansByHand()手动磨碎咖啡豆。autoMake()修改为判断isMotorDriven(),根据条件不同,选择是手动还是电动。flag为true,默认为电动。

子类实现:

public class Cappuccino extends AutoCoffee {
	//设为手动
	@Override
	protected boolean isMotorDriven() {	
		return false;
	}
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和奶泡");
	}
}


public class CaramelMacchiato extends AutoCoffee {
	@Override
	public void makeCoffee() {
		super.makeCoffee();
		System.out.println("加入牛奶和焦糖");
	}
}
Clinet.java
public class Clinet {
	public static void main(String[] args) {
			AutoCoffee cappuccino = new Cappuccino();
			AutoCoffee caramelMacchiato = new CaramelMacchiato();
			cappuccino.autoMake();
			System.out.println("------------------");
			caramelMacchiato.setFlag(false);
			caramelMacchiato.autoMake();
	}
}


卡布奇诺重写isMotorDriven()方法,返回false(而非flag属性),这样就只能是手动,客户端无法自己选择(即使调用setFlag()也不能改变),而焦糖玛奇朵是可以通过setFlag()来设置是手动还是电动。

结果如下:

获得咖啡豆

放入咖啡机

手动磨碎咖啡豆

冲咖啡

加入牛奶和奶泡

------------------

获得咖啡豆

放入咖啡机

手动磨碎咖啡豆

冲咖啡

加入牛奶和焦糖

模板方法还体现了一个重要的设计原则:好莱坞原则。但是很多人搞不清楚好莱坞原则和依赖倒置原则,改天另开博客,详述好莱坞原则、依赖倒置原则、控制反转和依赖注入的区别。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: