概述
在面向对象程序设计中,程序员往往会遇到这种情况:设计一个系统时知道算法所需的关键步骤,而且确定这些步骤的执行顺序,但某些步骤的具体实现还不知道,或者说某些步骤的实现与具体的环境相关
例如:去银行办理业务,一般要经过一下4个流程:取号,排队,办理业务,评价服务,其中取号,排队,评价服务的业务对每一个客户是一样的,可以在父类中实现,但是办理业务却因人而异,可能是存款,取款或者转账,这些可以延迟到子类中去实现
模板方法模式定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以在不改变该算法结构的情况下重定义该算法的某些特定步骤。
结构
模板方法 (template method) 模式中包含以下主要角色:
- 抽象类 (Abstract Class):负责给出一个算法的轮廓和骨架,由一个模板方法和若干个基本方法组成
- 模板方法:定义算法的骨架,按照某种顺序调用其包含的基本方法
- 基本方法:实现算法各个步骤的方法,是模板方法的组成部分,可以分为一下三种
- 抽象方法:方法的实现与具体环境有关,需要具体子类去实现
- 具体方法:通用的方法,声明和实现都在抽象类中完成
- 钩子方法:抽象类中实现,包含用于判断的逻辑方法( isXXX():boolean)和需要子类重写的空方法
- 具体子类 (Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,他们是一个顶级逻辑的组成步骤
案例
银行办理业务,一般要经过一下4个流程:取号,排队,办理业务,评价服务。现在通过模板方法来用代码模拟,类图如下:
代码实现:
AbstractClass 类:抽象类
process():模板方法 takeNumber(),quene(),work(),evaluation() 基本方法,其中 work() 属于抽象方法,其余均为具体方法
package com.atguigu.template;
public abstract class AbstractClass {
int number;
int amount;
int score;
public AbstractClass(int number,int amount,int score){
this.number = number;
this.amount = amount;
this.score = score;
}
//取号
public void takeNumber(){
System.out.println("请拿好您的号码:" + number);
}
//排队
public void quene(){
System.out.println("请排队,");
}
//办理业务
public abstract void work();
//评分
public void evaluation(){
System.out.println("这次服务您的评分为:" + score);
}
//模板方法
public void process(){
takeNumber();
quene();
work();
evaluation();
}
}
Deposit 类:存钱
package com.atguigu.template;
public class Deposit extends AbstractClass{
public Deposit(int number, int amount, int score) {
super(number, amount, score);
}
@Override
public void work() {
System.out.println("存款:" + amount + "元");
}
}
Withdraw 类:取钱
package com.atguigu.template;
public class Withdraw extends AbstractClass{
public Withdraw(int number, int amount, int score) {
super(number, amount, score);
}
@Override
public void work() {
System.out.println("取款:" + amount + "元");
}
}
Client 类:测试类
package com.atguigu.template;
public class Client {
public static void main(String[] args) {
//存款业务
Deposit deposit = new Deposit(9, 500, 8);
deposit.process();
System.out.println("---------------------");
//取款业务
Withdraw withdraw = new Withdraw(23, 1200, 9);
withdraw.process();
}
}
请拿好您的号码:9
请排队,
存款:500元
这次服务您的评分为:8
---------------------
请拿好您的号码:23
请排队,
取款:1200元
这次服务您的评分为:9
优点:
- 提高代码的复用性:将相同部分的代码放在抽象的父类中,将不同的代码放在不同的子类中
- 实现反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不用的行为,实现反向控制,符合 “开闭原则”
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计更加抽象
- 父类中的抽象方法有子类实现,子类执行的结构会影响父类的结果,导致一种反向的控制结构,提高代码阅读的困难
适用场景
算法的整体步骤很固定,但其中个别部分易变时,可以使用模板方法,将容易变的部分抽象出来,供子类实现
需要通过子类类决定父类算法中某个步骤是否执行,实现子类对父类的反向控制