寂然解读设计模式 - 模板模式

简介: 模板模式即在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
I walk very slowly, but I never walk backwards 

设计模式 - 模板模式


寂然

大家好,我是寂然,本节课我们进入到篇章五 - 行为型模式的学习,首先第一个,来聊行为模式中的模板模式,这个模式相对而言比较常见,同样,我们通过一个案例需求来引入

案例演示 - 豆浆制作

制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎

通过添加不同的配料,可以制作出不同口味的豆浆(花生,黑豆)

选材、浸泡和放到豆浆机打碎这几个步骤对于制作任意口味的豆浆都是一样的

请使用模板模式完成

(因为模板模式,比较简单,很容易想到,因此就直接应用,不再通过传统的方案来引出模板方法模式)

基本介绍

模板模式(Template Pattern),又叫模板方法模式(Template Method Pattern),他的基本介绍如下:

在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤

举例说明

概念听着云里雾里,其实这个模式是大家非常常用的,例如,实际开发中常常会遇到,代码骨架类似甚至相同,只是具体的实现不一样的场景,例如:流程都有开启、编辑、驳回、结束,每个流程都包含这几个步骤,不同的是不同的流程实例它们的内容不一样,再举个生活中的例子,共享单车都是先开锁、骑行、上锁、付款,这些大的步骤固定,不同的是每个实例的具体实现细节不一样。这些类似的业务我们都可以定义一个类,在其中一个方法把执行流程定义出来,例如先执行开锁,再执行骑行,接着,上锁和付款,并把公共的方法进行实现,例如共享单车开锁和关锁的方法,定义好了这样一个类似模板的类,那具体各个子类,也就是各个共享单车去实现业务逻辑就容易多了,其实这种思想,就是模板模式

原理类图


1609748193965.png


角色分析

AbstractClass

抽象类, 类中实现了模板方法(template),定义了算法的大致流程,具体子类需要去实现其它的抽象方法

ConcreteClassA/B

实现父类中的抽象方法,完成算法中特定子类的相关步骤

解决方案 - 模板模式

OK,同样的思路,我们先画出使用模板模式解决案例需求的类图,接着通过代码来实现

类图演示

1609750467616.png


代码演示
//抽象类 豆浆
public abstract class SoyaMilk {
​
 //make 即模板方法,可以做成final,不让子类去覆盖
 final void make(){
​
 select();
 add();
 soak();
 beat();
​
 }
​
 //选材
 void select(){
 System.out.println("选择新鲜的黄豆作为材料");
 }
​
 //添加配料,抽象方法
 abstract void add();
​
 //浸泡
 void soak(){
 System.out.println("将所有的材料浸泡一段时间");
 }
​
 //打碎
 void beat(){
 System.out.println("将所有的材料放到豆浆机里打碎进行制作");
 }
​
}
​
//黑豆豆浆
public class BlackBeanSoyaMilk extends SoyaMilk {
​
 @Override
 void add() {
 System.out.println("添加黑豆,糖等一系列材料");
 }
}
​
//花生豆浆
public class PeanutSoyaMilk extends SoyaMilk {
​
 @Override
 void add() {
 System.out.println("添加花生,糖等一系列材料");
 }
}
​
//客户端
public class Client {
​
 public static void main(String[] args) {
​
 //制作黑豆豆浆
 SoyaMilk blackBeanSoyaMilk = new BlackBeanSoyaMilk();
​
 blackBeanSoyaMilk.make();
​
 //制作花生豆浆
 SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
​
 peanutSoyaMilk.make();
 }
}

所以,你可以把一个通用的方法,全部提到抽象类中去写

钩子方法

下面,我们来看下模板模式中的钩子方法

在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为 ”钩子方法”

案例需求

希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

代码演示
//抽象类 表示豆浆
public abstract class SoyaMilk {
​
 //make 即模板方法,可以做成final,不让子类去覆盖
 final void make(){
​
 select();
 if (needBatching()){
 add();
 }
 soak();
 beat();
​
 }
​
 //选材
 void select(){
 System.out.println("选择新鲜的黄豆作为材料");
 }
​
 //添加配料,抽象方法
 abstract void add();
​
 //浸泡
 void soak(){
 System.out.println("将所有的材料浸泡一段时间");
 }
​
 //打碎
 void beat(){
 System.out.println("将所有的材料放到豆浆机里打碎进行制作");
 }
​
 //钩子方法,决定是否要添加配料
 boolean needBatching(){
 return true;
 }
}
​
//纯豆浆
public class PureSoyaMilk extends SoyaMilk {
​
 @Override
 void add() {
 //空实现
 //TODO...
 }
​
 @Override
 boolean needBatching() {
 return false; //不会再去加配料
 }
}
​
//客户端
public class Client {
​
 public static void main(String[] args) {
​
 //制作纯豆浆
 SoyaMilk pureSoyaMilk = new PureSoyaMilk();
​
 pureSoyaMilk.make();
​
 }
}

它可以很轻松的实现,某一个方法在你的模板里是否要调用,你可以设计这样一个钩子方法去进行调用,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,子类处理时提供了很大的灵活性

注意事项

基本思想

算法只存在于一个地方,也就是在父类中,容易修改,需要修改算法时,只要修改父类的模板方 法或者已经实现的某些步骤,子类就会继承这些修改,般模板方法都加上 final 关键字, 防止子类重写模板方法

优势

实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

既统一了算法,也提供了很大的灵活性(钩子方法),父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现

劣势

每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大

使用场景

当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理,例如上面的举例说明

IOC源码分析

源码分析

Spring IOC 容器初始化时,就会运用到模板方法模式 ,下面我们一起来看下

我们通过 ConfigurableApplicationContext 作为入口,可以看到,它是一个接口,这个接口里面定义了很多方法,其中有一个重要的方法 refresh() ,我们进到其实现来看,这个 refresh() 方法就是一个模板方法

@Override
 public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
 // Prepare this context for refreshing.
 prepareRefresh();
​
 // Tell the subclass to refresh the internal bean factory.
 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
​
 // Prepare the bean factory for use in this context.
 prepareBeanFactory(beanFactory);
​
 try {
 // Allows post-processing of the bean factory in context subclasses.
 postProcessBeanFactory(beanFactory);
​
 // Invoke factory processors registered as beans in the context.
 invokeBeanFactoryPostProcessors(beanFactory);
​
 // Register bean processors that intercept bean creation.
 registerBeanPostProcessors(beanFactory);
​
 // Initialize message source for this context.
 initMessageSource();
​
 // Initialize event multicaster for this context.
 initApplicationEventMulticaster();
​
 // Initialize other special beans in specific context subclasses.
 onRefresh();
​
 // Check for listener beans and register them.
 registerListeners();
​
 // Instantiate all remaining (non-lazy-init) singletons.
 finishBeanFactoryInitialization(beanFactory);
​
 // Last step: publish corresponding event.
 finishRefresh();
 }
​
 catch (BeansException ex) {
 if (logger.isWarnEnabled()) {
 logger.warn("Exception encountered during context initialization - " +
 "cancelling refresh attempt: " + ex);
 }
​
 // Destroy already created singletons to avoid dangling resources.
 destroyBeans();
​
 // Reset 'active' flag.
 cancelRefresh(ex);
​
 // Propagate exception to caller.
 throw ex;
 }
​
 finally {
 // Reset common introspection caches in Spring's core, since we
 // might not ever need metadata for singleton beans anymore...
 resetCommonCaches();
 }
 }
 }

可以看到,这个方法内部调了很多很多方法,并且调用的方法,是本类中定义好的,举例两个:

// Prepare this context for refreshing.
 prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

我们进入 obtainFreshBeanFactory() 方法,里面调用了两个抽象方法,refreshBeanFactory(),
getBeanFactory(),同样都是本类中定义好的,让子类去实现,这样具体要取哪种 BeanFactory 容器的决定权交给了子类,当然这里我们主要看模式的应用,不去关注IOC的初始化流程

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
 refreshBeanFactory();
 return getBeanFactory();
 }

那大家可以看到,和我们案例中的make() 方法很类似对吧,refresh() 里面定义了运行的大致流程


refresh() 里面有没有钩子方法呢?我们接着来看这两个方法

// Allows post-processing of the bean factory in context subclasses.
 postProcessBeanFactory(beanFactory);
​
// Initialize other special beans in specific context subclasses.
 onRefresh();

可以看到,postProcessBeanFactory() 里面都是空实现

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
 }

前面讲过,在模板方法模式里面,有些方法是空实现,主要价值是在于让子类覆盖他

protected void onRefresh() throws BeansException {
 // For subclasses: do nothing by default.
 }

同理,我在 refresh() 方法里面调用了 onRefresh() ,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,可以让子类处理的时候更加灵活,大家类比于我们案例中的制作纯豆浆,如果想要制作纯豆浆,那么子类重写方法,实现相应纯豆浆的业务逻辑

当然,ConfigurableApplicationContext 不止一个实现类,我们来看 GenericApplicationContext


1609759535325.png


这个类就对上面我们聊到的 refreshBeanFactory(),getBeanFactory() 都进行了实现

以及类 AbstractRefreshableApplicationContext,同样对 refreshBeanFactory(),getBeanFactory() 进行了实现


1609763028937.png


可能有人会说 AbstractRefreshableApplicationContext 是个抽象类啊,没关系,抽象类下面还有子类,我们在考虑设计模式的时候,主要学习它的一种思想,这个地方实现了不一定就是非抽象类,也可以是个抽象类,它的子类去进行实现

流程梳理

经过上面的分析,我们来进行梳理,首先,我们从接口 ConfigurableApplicationContext 进去,该接口声明了一个模板方法 refresh(),而 AbstractApplicationContext 对模板方法 refresh() 进行了实现,定义了运行的大致流程

同时,里面也存在钩子方法,钩子方法我们上面提到,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,钩子方法的使用可以让子类处理的时候更加灵活,当然AbstractApplicationContext是一个抽象类,所以他下面还有具体的子类,下面我们再来画下相应的类图

类图演示

1609763204435.png


refreshBeanFactory(),getBeanFactory() 是抽象方法
postProcessBeanFactory(beanFactory),onRefresh()是钩子方法,他们都在模板方法 refresh() 中被调用了,相信分析到这,大家应该明白模板模式在 IOC源码中的应用了

下节预告

OK,到这里,模板模式的相关内容就结束了,下一节,我们开启命令模式的学习,希望大家能够一起坚持下去,真正有所收获,就像开篇那句话,我走的很慢,但是我从来不后退,哈哈,那我们下期见~

相关文章
|
10天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
12天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
5天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
17 1
|
28天前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
19 3
|
2月前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:如何提高代码的可维护性与扩展性在软件开发领域,PHP 是一种广泛使用的服务器端脚本语言。随着项目规模的扩大和复杂性的增加,保持代码的可维护性和可扩展性变得越来越重要。本文将探讨 PHP 中的设计模式,并通过实例展示如何应用这些模式来提高代码质量。
设计模式是经过验证的解决软件设计问题的方法。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理地使用设计模式可以显著提高代码的可维护性、复用性和扩展性。本文将介绍几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并通过具体的例子展示如何在PHP项目中应用这些模式。
|
2月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
30天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
33 0
|
2月前
|
设计模式 Java
Java设计模式-工厂方法模式(4)
Java设计模式-工厂方法模式(4)