引入
在我们的日常生活中,喝咖啡和喝茶都需要烧开水、冲泡,然后倒入杯子里。其中烧开水和导入杯子里是固定动作,而中间冲咖啡还是泡茶叶取决于我们自己。因此在模板方法模式中,为提高代码的灵活性和复用性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计,在模板方法模式中,将实现功能的每一个步骤所对应的方法称为基本方法(例如:烧开水、倒入杯子)。而调用这些基本方法同时定义基本方法的执行次序的方法称为模板方法(如喝水的人的行为)。
那我们怎么设计呢,我们可以将基本方法写在父类中,至于中间的冲咖啡或泡茶叶我们只需要在父类中声明,具体的实现交给冲咖啡子类或者泡茶叶子类。因此客户端只需要选择一种子类调用即可。
概念
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
该模式的核心思想是通过将可变的部分抽象成接口,将不可变的部分封装在抽象类中。抽象类定义了一个模板方法,它包含了算法的结构和调用顺序,并且通过调用抽象方法来完成某些特定的步骤。具体子类继承抽象类,并实现其中的抽象方法,从而按照自己的需求修改或扩展算法的特定步骤。
1、 AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
2、ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
模板方法
模板方法是抽象类中定义的一个算法骨架,它规定了算法的结构和调用顺序,包含了若干个基本方法的调用。模板方法在抽象类中被声明为final,防止子类修改算法的整体流程。模板方法一般是由具体子类实现中的步骤所调用,它们可以是具体方法、抽象方法或钩子方法(Hook Method)。
基本方法
基本方法是模板方法中调用的具体方法,它们是模板方法的组成部分,用于实现算法的特定步骤。基本方法可以被模板方法直接调用,也可以被子类重写以实现特定的功能。基本方法一般是具体方法、抽象方法或钩子方法组成。
1、 抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。
2、具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
3、钩子方法:一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现(可使用virtual关键字将其定义为虚函数),并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现。
第一类钩子方法:可以在方法中返回一个bool的值,用来判断xxx是否可执行
第二类钩子方法:用virtual来修饰的一个空的实现方法{},这样的话如果子类没有覆盖父类的钩子方法,编译不会报错。
ps:如何区分:在模板方法中,如果调用的方法为抽象方法,则该方法为抽象方法;如果调用的方法是空实现,那么该方法为钩子方法。
在模板方法模式中,由于面向对象的多态性,子类对象在运行时将覆盖父类对象,子类中定义的方法也将覆盖父类中定义的方法,因此程序在运行时,具体子类的基本方法将覆盖父类中定义的基本方法,子类的钩子方法也将覆盖父类的钩子方法,从而可以通过在子类中实现的钩子方法对父类方法的执行进行约束,实现子类对父类行为的反向控制。
示例
我们以做饭为例子
首先定义抽象类,此类包含模板方法cook、钩子方法isHungry、普通方法等。其中烹饪类cookDish是抽象方法,由具体的子类来实现,至此模板方法流程构建完成。
其次定义子类来集成抽象类,子类可继承全部的方法,并且要实现父类的抽象方法。
// 抽象类:烹饪过程
abstract class CookingProcess {
// 模板方法:烹饪
public final void cook() {
prepareIngredients();
if (isHungry()) {
cookDish();
serveDish();
}
}
//钩子函数:判断饿不饿
protected boolean isHungry() {
return true;
}
// 基本方法:准备食材
protected void prepareIngredients() {
System.out.println("准备食材");
}
// 抽象方法:烹饪菜肴
protected abstract void cookDish();
// 基本方法:上菜
protected void serveDish() {
System.out.println("上菜");
}
}
// 具体类:炒菜
class StirFryDish extends CookingProcess {
// 实现烹饪菜肴方法
@Override
protected void cookDish() {
System.out.println("炒菜");
}
//覆写钩子函数
@Override
protected boolean isHungry() {
return false;
}
}
// 具体类:蒸菜
class SteamedDish extends CookingProcess {
// 实现烹饪菜肴方法
@Override
protected void cookDish() {
System.out.println("蒸菜");
}
}
public class TemplateMethodPatternExample {
public static void main(String[] args) {
CookingProcess stirFryDish = new StirFryDish();
stirFryDish.cook();
System.out.println("------------");
CookingProcess steamedDish = new SteamedDish();
steamedDish.cook();
}
}
spring源码实现
在IOC容器中有一个ConfigurableApplicationContext接口,这个接口被AbstractApplicationContex抽象类实现,同时也是实现了refresh()方法,此方法即为模板方法
//AbstractApplicationContex类
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
//1、obtainFreshBeanFactory为抽象方法,具体实现见下文
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
//2、钩子方法
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
//3、钩子方法
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
//1、抽象方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
//2、钩子方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
//3、钩子方法
protected void onRefresh() throws BeansException {
}
//其余为普通方法
从上述模板方法中可以看出,钩子方法的特点是空实现,即使子类不覆写也不会报错,而抽象方法子类必须实现
AbstractApplicationContex的子类GenericApplicationContext,此类实现了父类的抽象方法obtainFreshBeanFactory()中的refreshBeanFactory()和getBeanFactory()
protected final void refreshBeanFactory() throws IllegalStateException {
if (!this.refreshed.compareAndSet(false, true)) {
throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
} else {
this.beanFactory.setSerializationId(this.getId());
}
}
public final ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
类图解析
总结
优点:
1、提高代码复用性:将共同的代码抽取到模板方法中,不同的实现可以在具体子类中重写相关方法,从而避免了重复编写相同的代码。
2、提供了灵活性:模板方法定义了算法的骨架,但允许具体子类根据需要进行自定义实现,使得整个算法可以适应不同的变化。
3、便于扩展:通过添加新的具体子类来扩展功能或修改算法的某些部分,而无需更改模板方法本身。
缺点:
1、增加了类的数量:使用模板方法模式需要定义抽象类和具体子类,可能会增加类的数量,使得代码结构变得复杂。
2、可能导致继承滥用:如果不合理地使用继承,而是过度依赖抽象类和具体子类之间的继承关系,可能会导致设计上的问题。
适用场景:
1、当存在一组相似的算法或操作,其中大部分步骤是相同的,只有部分步骤需要定制化时,可以考虑使用模板方法模式。
2、当希望封装算法的整体流程,将可变的部分留给具体子类来实现时,可以使用模板方法模式。
3、当希望通过扩展具体子类来增加新的功能或修改算法的特定部分时,模板方法模式也是一种合适的选择。
总之,模板方法模式适用于具有相似算法流程并且需要定制化某些步骤的场景,能够提高代码复用性、灵活性和可扩展性。