模板方法模式在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。假设一些工厂生产一些产品,虽然生产的产品不同,但有些步骤是相同的,只是部分步骤不同,那么可以使用抽象类的形式,公共步骤则在抽象类中具体写出,而不同步骤则在抽象类中以抽象方法的形式表达。等到实际产品中再具体化这些抽象方法,而子类中不需要再重复那些公共方法,这样可以避免代码冗杂。这利用了继承的特性,继承机制中子类可以拥有父类中可以访问到的方法、变量等。同时也利用了抽象类的特性,可以将方法延迟到子类中实现。
1.适用性和优缺点
1.适用性:
a.一次性实现算法不变的部分,并将可变的部分留给子类实现。
b.各子类中公共的行为被提取出来并集中到一个公共父类中以避免代码重复。首先识别现有代码中的不同之处,并且将不同之处分理出新的操作。最后用一个调用这些新的操作的模板方法来替代这些不同的代码。
c.控制子类扩展。
2.优点:
a.去除子类中重复代码
b.子类实现算法的某些细节,有助于算法的扩展。
c.通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合"开放-封闭"原则。
3.缺点:
每个不同的实现都需要定义一个子类,这会导致类的个数增加,设计更加抽象。
2.示例说明
这里讲解的示例是一个饮料制作工厂的示例,这里饮料制作好多步骤都相似,比如都需要煮等,所以先将每种饮料的制作过程建立一个类,这里主要有咖啡和茶的制作过程。
package caffeinebeverage;
import java.util.*;
import java.io.*;
public class CoffeeWithHook extends CaffeineBeverageWithHook {
@Override
void brew() {
// TODO Auto-generated method stub
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
// TODO Auto-generated method stub
System.out.println("Adding Sugar and Milk");
}
public boolean customerWantsCondiments()
{
String answer=getUserInput();
if(answer.toLowerCase().startsWith("y"))
{
return true;
}
else
{
return false;
}
}
public String getUserInput()
{
String answer=null;
System.out.println("Would you like milk and sugar with your coffee(y/n)?");
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
try
{
answer=in.readLine();
}
catch(IOException ioe)
{
System.out.println("IO error trying to read your answer");
}
if(answer==null)
{
return "no";
}
return answer;
}
}
package caffeinebeverage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TeaWithHook extends CaffeineBeverageWithHook {
@Override
void brew() {
// TODO Auto-generated method stub
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
// TODO Auto-generated method stub
System.out.println("Adding Lemon");
}
public boolean customerWantsCondiments()
{
String answer=getUserInput();
if(answer.toLowerCase().startsWith("y"))
{
return true;
}
else
{
return false;
}
}
public String getUserInput()
{
String answer=null;
System.out.println("Would you like lemon your tea(y/n)?");
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
try
{
answer=in.readLine();
}
catch(IOException ioe)
{
System.out.println("IO error trying to read your answer");
}
if(answer==null)
{
return "no";
}
return answer;
}
}
以上是制作咖啡和制作茶的两个类,其中两个类公共步骤是boilWater和pourInCup两个步骤,所以将其提取出来放进抽象类中,而其它步骤则在抽象类中以抽象方法的形式出现,另外在抽象方法中建立一个模板方法,用于将这些步骤集合调用。这就是模板方法模式的精髓。以下是抽取的抽象类:
package caffeinebeverage;
public abstract class CaffeineBeverageWithHook {
void prepareRecipe()
{
boilWater();
brew();
pourInCup();
if(customerWantsCondiments())
{
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater()
{
System.out.println("Boiling water");
}
void pourInCup()
{
System.out.println("Pouring into cup");
}
boolean customerWantsCondiments()
{
return true;
}
}
然后是客户端机,进行选择制作饮料,但不需要访问所有步骤,只需要实例化相应的饮料类,并调用模板方法即可,并不需要调用那些表示制作过程的具体步骤。这个模板方法相当于封装了制作饮料的具体步骤方法。以下是客户端代码:
package caffeinebeverage;
public class BeverageTestDrive {
public static void main(String[] args) {
// TODO Auto-generated method stub
TeaWithHook teaHook=new TeaWithHook();
CoffeeWithHook coffeeHook=new CoffeeWithHook();
System.out.println("\nMaking tea...");
teaHook.prepareRecipe();
System.out.println("\nMaking coffee...");
coffeeHook.prepareRecipe();
}
}
运行结果如下:
Making tea...
Boiling water
Steeping the tea
Pouring into cup
Would you like lemon your tea(y/n)?
y
Adding Lemon
Making coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Would you like milk and sugar with your coffee(y/n)?
n
以上示例讲述了泡咖啡和泡茶的过程,两者冲泡的流程基本相同,只是其中部分流程细节有差异,所以可以在抽象类中将其提取成一个模板方法,该模板方法是指冲泡的过程,其中有部分步骤是公共步骤,可以在抽象类中声明,但细节有差异的则在抽象类中以抽象方法声明,在具体类中实现。使用模板方法可以使高层类调用低层类的方法,降低依赖性。
3.与策略模式的比较
相同之处:两者都定义了算法族。
不同之处:策略模式采用组合形式,运行时改变算法,更有弹性。而模板方法使用继承的形式,更有效率,需要更少对象,对算法能够更好的控制。但是依赖程度较高。