现实生活中有很多可以使用模板方法的例子,比如流水线上的任务,做饭的先后次序,网上买个东西,都可以分为好几步来走,其中相同的部分就可以提取成模板方法。
1. 定义
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
2. UML类图
模板方法模式的UML类图很简单,就是一个抽象实现和一个具体实现类的继承关系,也是最简单的行为性设计模式。
AbstractClass: 抽象模板方法templeteMethod定义了模板方法的使用流程,可以包含非抽象方法和抽象方法,所以只能是抽象类,不能是接口。
ConcreteClass: 具体实现类,模板方法的一种具体实现,根据模板方法预定义的流程实现具体的步骤。
撸一下代码:
AbstractClass:
public abstract class AbstractClass {
//final关键字保证模板方法不会被子类重写
public final void templateMethod() {
operation1();
operation2();
operation3();
if(runOperation4()) {
operation4();
}
}
/**
* 具体方法
*/
public void operation1() {
System.out.println("AbstractClass operation1");
}
/**
* 抽象方法
*/
public abstract void operation2();
/**
* 具体方法--钩子方法
*/
public void operation3(){
}
/**
* 钩子方法
*/
public boolean runOperation4() {
return true;
}
public void operation4(){
System.out.println("AbstractClass operation4");
}
}
其中模板方法被声明为final类型,保证模板方法的不可修改性,防止子类重写模板方法,修改具体的流程。
抽象类定义了四种方法,分别为具体方法、抽象方法、和钩子方法。所谓钩子方法是可以用子类控制父类的行为。
再来看一下具体实现类ConcreteClass:
public class ConcreteClass extends AbstractClass {
@Override public void operation2() {
System.out.println("ConcreteClass operation2");
}
@Override public void operation3() {
System.out.println("ConcreteClass operation3");
}
@Override public boolean runOperation4() {
return false;
}
public static void main(String[] args) {
AbstractClass concrete1 = new ConcreteClass();
concrete1.templateMethod();
}
}
具体实现类对父类中的不同方法可以有不同的处理方式
对具体方法operation1()没有做任何修改,方法实现与父类中相同。
operation2()是抽象方法,必须由子类实现。
operation3()在父类中是一个空实现,由子类重写。因为方法由子类进行了重写,控制了父类的行为,这也是一个钩子方法。
runOperation4()是一个钩子方法,通过重写runOperation4()来控制是否进行operation4()。实现了由子类来控制父类的行为。
3. 示例
生活中有很多可以使用模板方法的例子,做很多事情都是有先后次序或是流水线,这时候都应该使用模板方法来将统一的共同实现提取成抽象类的普通方法,将子类的不同实现在父类声明为抽象方法,对复杂的流程可以使用钩子方法进行具体的控制。
举个栗子:程序员上班的一天和休息的一天
上班的一天:每天早上吃早饭,白天在公司搬砖,晚上加班,回来以后没有闲时间娱乐,只能睡觉
休息的一天:早上要睡到11点不需要吃早饭,白天约朋友粗去玩~,晚上在家里玩游戏,然后睡觉
模板方法类:
public abstract class ProgrammerDay {
public final void templateMethod() {
if (eatBreakfast()) {
haveBreakfast();
}
daytimeThing();
nightThing();
sleep();
}
public boolean eatBreakfast() {
return true;
}
public void haveBreakfast() {
System.out.println("吃早饭");
}
public abstract void daytimeThing();
public void nightThing(){
}
public void sleep() {
System.out.println("睡觉");
}
}
程序员工作的一天:
public class WorkDay extends programmerDay {
@Override public void daytimeThing() {
System.out.println("努力搬砖~~");
}
}
程序员休息的一天:
public class RestDay extends programmerDay {
@Override public boolean eatBreakfast() {
return false;
}
@Override public void daytimeThing() {
System.out.println("约朋友粗去玩~~");
}
@Override public void nightThing() {
System.out.println("在家里玩游戏~~");
}
}
使用时:
public static void main(String[] args) {
ProgrammerDay day1 = new WorkDay();
day1.templateMethod();
ProgrammerDay day2 = new RestDay();
day2.templateMethod();
}
输出的结果:
工作的一天:
吃早饭
努力搬砖~~
睡觉
休息的一天:
约朋友粗去玩~~
在家里玩游戏~~
睡觉
工作的一天明显没有休息的一天过的爽啊。。
示例的方法中eatBreakfast()和nightThing()是钩子方法,可以由子类来控制父类的行为。sleep()是普通方法。daytimeThing()是抽象方法~
4. 使用反射创建具体类
仔细观察上面使用时的代码,除了创建具体对象时的代码不同,其他情况下都是相同的。在创建继承自同一个父类的方法时,都可以使用反射来优化创建的流程。
public static void main(String[] args) {
ProgrammerDay day3 = createDay(RestDay.class);
day3.templateMethod();
}
public static <T extends ProgrammerDay> T createDay(Class<T> clz) {
ProgrammerDay p = null;
try {
p = (ProgrammerDay) Class.forName(clz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) p;
}
这样我们在创建具体类时,只需要传入具体类的类型就可以了~
5. 总结
模板方法模式是基于继承的代码复用技术,它体现了面向对象的诸多重要思想,是一种使用较为频繁的模式。模板方法模式广泛应用于框架设计中,以确保通过父类来控制处理流程的逻辑顺序(如框架的初始化,测试流程的设置等)。
模板方法模式的主要优点如下:
(1) 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
(2) 模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
(3) 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
(4) 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。
参考文献:
https://blog.csdn.net/carson_ho/article/details/54910518
https://blog.csdn.net/LoveLion/article/details/8299863
https://blog.csdn.net/LoveLion/article/details/8299927
《大话设计模式》
《Android源码设计模式》