模板模式在诸多模式中算是比较简单的一个,即便一个新手,也能很快的上手和使用。虽然简单,但合理的在项目中使用带来的好处可不小。软件设计中有个很重要的原则:DRY(Don't Repeat Yourself),重复有很多种,比如代码重复,逻辑重复,而模板模式的最拿手的就是消除逻辑重复。
我们先看一下模板模式的定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中去实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。只要你学习过任何一门面向对象语言,那么理解上面的定义肯定不是问题。
来看看模式的UML类图:
AbstractClass是一个抽象类,其中templateMethod是一个final方法,封装了具体的算法实现步骤、过程,其内部调用了其它方法。abstractMethod1与abstractMethod2是抽象方法,其具体实现由具体的子类决定,这样子类就可以在不改变算法结构的情况下,重新定义算法中的某些步骤。值得一提的是hook方法,它是一个钩子方法,在父类中会提供一个默认的实现,子类可以有选择的决定是否要覆盖钩子方法。简单而言,钩子让子类有机会修改算法中的某些步骤,是否使用钩子完全取决于业务。
我们来看一个例子。
饮料 | 牛奶 | 咖啡 |
1. 把杯子洗干净。 2. 把水煮沸。 3. 把饮料放入杯子。 4. 把开水倒入杯中。 5. 加调料。 6. 搅拌。 | 1. 把杯子洗干净。 2. 把水煮沸。 3. 把奶粉放入杯子。 4. 把开水倒入杯中。 5. 加糖。 6. 搅拌。 | 1. 把杯子洗干净。 2. 把水煮沸。 3. 把咖啡放入杯子。 4. 把开水倒入杯中。 5. 加糖、伴侣。 6. 搅拌。 |
饮料(Beverage是一个抽象类,make方法封装了饮料的制作流程,但具体制作什么饮料,加不加调料、加什么调料由给子类去决定,addCondiment方法是一个钩子,默认什么都不做。
public abstract class Beverage {
public final void make() {
System.out.println("把杯子洗干净。");
System.out.println("把水煮沸。");
this.addBeverage();
System.out.println("把水倒入杯中。");
this.addCondiment();
System.out.println("搅拌。");
}
protected abstract void addBeverage();
protected void addCondiment() {
//默认什么调料都不加。
}
}
牛奶(Milk是一个实现类,仅实现addBeverage方法;Coffee也是一个实现类,实现了addBeverage,并且覆盖了addCondiment方法(不加糖咖啡太苦啦)。
public class Milk extends Beverage {
@Override
protected void addBeverage() {
System.out.println("把奶粉倒入杯口。");
}
}
public class Coffee extends Beverage {
@Override
protected void addBeverage() {
System.out.println("把咖啡倒入杯口。");
}
@Override
protected void addCondiment() {
System.out.println("加糖和咖啡伴侣。");
}
}
运行结果:
public static void main(String[] args) {
Beverage milk = new Milk();
Beverage coffee = new Coffee();
System.out.println("做牛奶......");
milk.make();
System.out.println("\n做咖啡......");
coffee.make();
}
做牛奶......
把杯子洗干净。
把水煮沸。
把奶粉倒入杯口。
把水倒入杯中。
搅拌。
做咖啡......
把杯子洗干净。
把水煮沸。
把咖啡倒入杯口。
把水倒入杯中。
加糖和咖啡伴侣。
搅拌。
JDK中有哪些使用模板模式的部分?
像Collections.sort、Arrays.sort等方法,都需要一个实现Comparator接口的对象来决定如何比较对象(实现compare方法);
又如File.listFiles方法需要一个实现FileFilter接口的对象来决定如何过滤文件(通过实现accept方法),在JDK中类似的场景还有很多,这里就不一一例举了。