概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
定义
定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类不改变一个算法的结构即可重定义算法的某些特定步骤。
举个栗子
一个武侠要战斗的时候,也有一套固定的通用模式,那就是运行内功,开启经脉,准备武器和使用招式,我们把这些用代码表示
public abstract class AbstractSwordsman {
//该方法为final,防止算法框架被覆写
public void finghting(){
//运行内功,抽象方法
neigong();
//调整经脉,具体方法
jingmai();
//如果有武器,则准备武器
if (hasWeapons()){//2
weapons();
}
//使用招式
moves();
//钩子方法
hook();//1
}
//空实现方法
protected void hook(){}
protected abstract void neigong();
protected abstract void weapons();
protected abstract void moves();
protected void jingmai() {
System.out.println("开启经脉");
}
/*是否有武器的方法*/
protected boolean hasWeapons(){
return true;
}
}
这个抽象类包含了三种类型的方法,分别是抽象方法,具体方法和钩子方法。(钩子方法:由抽象类声明并且实现,子类也可以选择加以扩展。通常抽象类会给出一个空的钩子方法,也就是没有实现的扩展。它和具体方法在代码上没有区别,不过是一种意识的区别;而它和抽象方法有时候也是没有区别的,就是在子类都需要将其实现的时候。而不同的是抽象方法必须实现,而钩子方法可以选择性实现或者不实现。也就是说钩子方法为你在实现某一个抽象类的时候提供了可选项,相当于预先提供了一个默认配置。)在上面的例子中就是武侠开启经脉的方式都一样,所以就在具体的方法中实现。钩子方法则分为两类:第一类在上面代码的注释1处,它有一个空实现的方法,子类可以视情况来决定是否要覆盖它;第二类在注释2处,这类钩子方法的返回类型通常是boolean类型的,其一般用于对某个条件进行判断,如果条件满足则执行某一步操作,否则将不执行。
具体实现类
武侠以张无忌和张三丰来举例子
public class ZhangWuJi extends AbstractSwordsman {
private static final String TGA = "张无忌";
@Override
protected void neigong() {
System.out.println(TGA+"运行九阳神功");
}
@Override
protected void weapons() {
}
@Override
protected void moves() {
System.out.println(TGA+"使用招式乾坤大挪移");
}
@Override
protected boolean hasWeapons() {
return false;
}
}
public class ZhangSanFeng extends AbstractSwordsman {
private static final String TGA = "张三丰";
@Override
protected void neigong() {
System.out.println(TGA+"运行纯阳无极功");
}
@Override
protected void weapons() {
System.out.println(TGA+"使用太极剑");
}
@Override
protected void moves() {
System.out.println(TGA+"使用招式太极拳");
}
@Override
protected boolean hasWeapons() {
return true;
}
@Override
protected void hook() {
System.out.println(TGA+"突然肚子不舒服,老夫先去趟厕所");
}
}
张无忌没有武器,所以hasWeapons为false这样也不会进入weapons方法了,张三丰突然感觉肚子不舒服,所以就实现了钩子方法hook,用来处理一些自定义的逻辑。
客户端调用
public class Client {
public static void main(String[] args) {
ZhangWuJi zhangWuJi = new ZhangWuJi();
zhangWuJi.finghting();
ZhangSanFeng zhangSanFeng = new ZhangSanFeng();
zhangSanFeng.finghting();
}
}
模板方法模式的使用场景和优缺点
- 使用场景
- 多个子类有共有的方法,并且逻辑基本相同时。
- 面对重要,复杂的算法,可以把核心算法设计为模板方法,周边相关细节功能则由各个子类实现
- 需要通过子类来决定父类算法中的某个步骤是否执行,实现子类对父类的反向控制
- 优点
- 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
- 子类实现算法的某些细节,有助于算法的扩展
- 缺点
- 每个不同的实现都需要定义一个子类,这会导类的个数的增加,设计更加抽象。
借鉴刘望舒《Android进阶之光》