目录
1、模板方法模式的定义
模板方法模式定义了一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
2、模板方法模式举例及使用原因
2.1 例子
在我们生活中,其实许多地方都是模板方法模式,比如①我们大家平时看病的时候,肯定会先挂号→排队等叫号→看病→付款→拿药。其中这个过程中,其实只有看病的过程是取决于我们得了什么病,是不确定的,而其他过程都是固定的流程,因此就可以用模板方法模式。②又比如我们设置闹钟的时候,通常会解锁手机→打开“时钟”→设置闹钟时间→关闭手机。这个过程实际上只有“设置闹钟时间”环节是不确定的,其他流程是固定的,因此也可以用模板方法模式。
2.2 使用原因
大家可以发现,如果我们上述不通过模板方法模式实现的话,我们要写每个病人看病的流程应该怎么样?是不是必须每个病人都重复写挂号、排队等叫号、付款、拿药等一样的流程,是不是就造成代码看起来很臃肿?但是如果我们使用模板方法模式的话,只需要写看病的流程即可了,大大减少了代码量且提高了代码的复用度。而且这种如果此时又来一个病人的话,看的病不一样,只需多加一个类即可,满足我们的“开闭原则”。
3、模板方法模式实现
3.1 模板方法模式结构
模板方法(Template Method)模式包含以下主要角色:
①抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
②具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级
逻辑的组成步骤。
3.2 模板方法模式举例分析
我们本文代码实现例子基于上述我们说的“看病过程”:挂号→排队等叫号→看病→付款→拿药。其中显然只有“看病”的流程是不确定的,因此我们可以基于这个例子来通过模板方法实现。
3.3 代码实现
抽象病人类(抽象类):
public abstract class AbstractPatient {//抽象类
//通常我们为了防止恶意修改,一般来说模板定下来就不变了,因此模板方法用final修饰
public final void TreatmentProcedure(){ //就诊流程(模板方法)
//将我们的普通方法组合起来
Registration();
WaitInLine();
Treatment();
Pay();
GetMedicine();
}
private void Registration(){ //挂号
System.out.println("挂号......");
}
private void WaitInLine(){ //等待叫号
System.out.println("等待叫号......");
}
public abstract void Treatment(); //看病
private void Pay(){ //付款
System.out.println("付款......");
}
private void GetMedicine(){ //取药
System.out.println("取药......");
}
}
病人①类(具体子类):
public class PatientOne extends AbstractPatient{ //病人1
@Override
public void Treatment() { //病人1的看病流程
System.out.println("发烧了,先挂瓶然后吃退烧药");
}
}
病人②类(具体子类):
public class PatientTwo extends AbstractPatient { //病人2
@Override
public void Treatment() {//病人2的看病流程
System.out.println("风寒了,拔罐、针灸......");
}
}
测试类:
public class Test {
public static void main(String[] args) {
PatientOne patientOne = new PatientOne();
PatientTwo patientTwo = new PatientTwo();
System.out.println("以下是病人①的看病流程:");
patientOne.TreatmentProcedure();
System.out.println("----------分界线----------");
System.out.println("以下是病人②的看病流程:");
patientTwo.TreatmentProcedure();
}
}
运行流程:
可见,我们通过模板模式实现了以上例子,这样如果我们又有新的看病流程的人,仅需新建一个具体子类即可,而且大家看上述我们举例的两个子类代码是不是非常简洁,并没有看起来很臃肿。
3.4 不足点分析
虽然我们通过上述模板方法模式实现我们的例子,能够使代码很简洁,但是大家发现没有,上述代码存在一个很大的问题!那就是:实际上每个人看病流程基本都不一样,全世界那么多人,我们是不是每个人都要创建一个类,是不是就发生了“类爆炸”!更有可能因为类创建、加载过多导致方法区OOM(OutOfMemory)!因此作者根据自己理解,我们一起来改进上述代码。
3.5 代码改进
我们可以这样来改进我们的代码:
看病方法接口:
public interface Treatment { //看病接口
public void treat();
}
病人类:
public class Patient{//病人类
private Treatment treatment;
public Patient(Treatment treatment){
this.treatment=treatment;
}
//通常我们为了防止恶意修改,一般来说模板定下来就不变了,因此模板方法用final修饰
public final void TreatmentProcedure(){ //就诊流程(模板方法)
//将我们的普通方法组合起来
Registration();
WaitInLine();
treatment.treat();
Pay();
GetMedicine();
}
private void Registration(){ //挂号
System.out.println("挂号......");
}
private void WaitInLine(){ //等待叫号
System.out.println("等待叫号......");
}
private void Pay(){ //付款
System.out.println("付款......");
}
private void GetMedicine(){ //取药
System.out.println("取药......");
}
}
测试类:
public class Test {
public static void main(String[] args) {
Patient patient1 = new Patient(new Treatment() { //仅需传入看病的那个过程的方法即可
@Override
public void treat() {
System.out.println("发烧了,先挂瓶然后吃退烧药......");
}
});
patient1.TreatmentProcedure();
System.out.println("----------分界线----------");
Patient patient2 = new Patient(new Treatment() {
@Override
public void treat() {
System.out.println("风寒了,拔罐、针灸......");
}
});
patient2.TreatmentProcedure();
}
}
运行结果:
当然,我们这种方法也可以用lambda表达式来简化我们上述匿名类,这里就不做过多解释了。
可见,我们通过这种方法不仅继承了我们一开始那种写法的优点,使代码不会臃肿,复用代码。而且还解决了我们上述说的类爆炸问题。
(这种想法是作者自己想的,如果有不对的地方大家欢迎指正,当然这种写法我个人感觉和“策略模式”的思想有点像,有点像策略模式和模板模式的结合)
4、模板方法模式优缺点
优点:
- 提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
5、模板方法模式应用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。