HeadFirst设计模式(九) - 模板方法模式
HeadFirst设计模式(九) - 模板方法模式
XuePeng77 发表于2年前
HeadFirst设计模式(九) - 模板方法模式
  • 发表于 2年前
  • 阅读 23
  • 收藏 1
  • 点赞 0
  • 评论 0

移动开发云端新模式探索实践 >>>   

摘要: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

直接举个例子

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

    咖啡的冲泡法:

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

    茶的冲泡法:

  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(),它负责调用具体的制作步骤,由子类自己实现。

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

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

    那么,我们也将prepareRecipe()方法进行抽象,现在就来看看怎么做……

    将冲泡定义一个抽象方法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();
	}
}

好莱坞原则

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

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

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 39
博文 127
码字总数 171367
×
XuePeng77
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: