模板方法模式
模板方法模式属于行为型模式,定义:一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
例如:去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
模板方法结构
主要角色:
抽象类(Abstract class)
负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成:
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method)∶在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。(一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXx,返回值类型为boolean类型)
具体子类(Concrete Class)︰
实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
案例实现
银行业务的步骤是固定的,分为取号、排队、办理具体业务、对银行工作人员进行评分等步骤。现通过模板方法模式来用代码模拟。
- 抽象银行类
package template;
/**
* @author pangjian
* @ClassName AbstractBank
* @Description 抽象银行,定义模板方法和具体方法
* @date 2021/10/18 13:08
*/
public abstract class AbstractBank {
/**
* @Description:定义模板方法,用final修饰防止本身算法被重写,
* 子类不需要关注银行整个复杂的业务流程,而只关注本身的核心业务即可
* @return void
* @date 2021/10/18 13:16
*/
public final void bankBusiness() {
quHao();
paiHao();
business();
}
public void paiHao() {
System.out.println("排号");
}
/**
* @Description:对所有操作都一样,定义为基本方法中的具体方法
* @return void
* @date 2021/10/18 13:13
*/
public void quHao() {
System.out.println("取号");
}
/**
* @Description:业务是不一样的,定义为基本方法中的抽象方法
* @return void
* @date 2021/10/18 13:12
*/
public abstract void business();
}
- 具体子类
package template;
/**
* @author pangjian
* @ClassName CreditBank
* @Description 具体子类,信贷银行,业务有贷款,继承抽象类
* @date 2021/10/18 13:18
*/
public class CreditBank extends AbstractBank {
@Override
public void business() {
System.out.println("现在执行的业务是贷款");
}
}
package template;
/**
* @author pangjian
* @ClassName SecuritiesBank
* @Description 具体子类,证券银行,业务是买基金,继承抽象类
* @date 2021/10/18 13:21
*/
public class SecuritiesBank extends AbstractBank {
@Override
public void business() {
System.out.println("现在办理的业务是买基金");
}
}
- 客户端
package template;
/**
* @author pangjian
* @ClassName Client
* @Description 客户端
* @date 2021/10/18 13:24
*/
public class Client {
public static void main(String[] args) {
// 买基金
SecuritiesBank securitiesBank = new SecuritiesBank();
securitiesBank.bankBusiness();
// 贷款
CreditBank creditBank = new CreditBank();
creditBank.bankBusiness();
}
}
优缺点
优点:
- 提高代码复用性,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
- 实现了反向控制,通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合"开闭原则"。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
JDK源码解析
InputStream类就用了模板方法模式
public abstract class InputStream implements Closeable {
// 基本方法中的抽象方法,由子类实现
public abstract int read() throws IOException;
// 重载方法
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
// 重载方法
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
// 模板方法(已经实现了框架算法逻辑):调用了无参的抽象read方法,该方法会被子类重写,如果被调用了,就会实现控制反转(一般都是子类调用父类方法),父类方法调用子类具体实现
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}