【java】设计模式之模板方法模式

模板方法模式:由子类决定如何实现父类算法中的哪一步。

例子:咖啡机自动冲咖啡,但是我们可能做卡布奇诺(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()来设置是手动还是电动。

结果如下:

获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和奶泡
------------------
获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和焦糖


模板方法还体现了一个重要的设计原则:好莱坞原则。但是很多人搞不清楚好莱坞原则和依赖倒置原则,改天另开博客,详述好莱坞原则、依赖倒置原则、控制反转和依赖注入的区别。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值