19.严丝合缝的文明——模板方法模式详解

2024-03-24 20:04

本文主要是介绍19.严丝合缝的文明——模板方法模式详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述
“项目评审的节点又快到了,PPT你写了没?”
“Oops,忘了,有模板没?给我一份”

概述

模板,一个频繁出现在办公室各类角色口中的词,它通常意味着统一、高效、经验和优质。各项汇报因为PPT的模板变得更加生动,各式的报告因为有了文档模板变得更加规范。找规律是人类很愿意钻研的工作之一,那么在设计模式中,是否有一种模板能够帮我们解决一些问题呢?
答案是肯定的,有!
在这里插入图片描述


一言

模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重新定义该算法的某些特定步骤。


炼金术

在这里插入图片描述
为了更好的理解模板模式,我想起了这样一个场景。我们都玩过打怪升级的养成类游戏,拿到了野外的原料再去城里的铁匠铺锻造,不同的材料会合成不同的装备。
如果让你用代码实现这个需求,你有什么样的思路?
我们只要稍加思索就会发现,这个需求中大部分的过程都是不变的,唯一的变数就在于投入的原料,在这样的场景下,模板方法模式就非常契合我们的需求了。


原理

在这里插入图片描述

  1. AbstractClass抽象类,类中实现了模板方法,定义了算法骨架,具体子类需要实现其它抽象方法operation2,3,4;
  2. ConcreteClass实现抽象方法operation2,3,4,以完成算法中特定子类的步骤;

简单实现

铁匠铺

public abstract class BlackSmith {final void make(){fire();putMaterial();hit();}void fire(){System.out.println("锻造炉升温");}abstract void putMaterial();void hit(){System.out.println("开始制作");}
}

铸剑室

public class SwordBlackSmith extends BlackSmith{@Overridevoid putMaterial() {System.out.println("放入铸剑材料:铁矿石");}
}

制弓室

public class ArchBlackSmith extends BlackSmith{@Overridevoid putMaterial() {System.out.println("放入制弓材料:木材,牛筋");}
}

在这里插入图片描述


钩子方法

上面的实现通俗易懂,下面我们思考一下,模板方法中,如果我需要视情况规避掉其中几个方法的执行,应该如何实现?
比如说:”冬天到了,铁匠铺没有生意,但是铁匠很冷,他只想让锻造炉升温,但是不需要放入任何锻造材料“。你有什么思路?
实际上这种需求就可以用钩子方法来实现,下面我们在原来的实现上稍加处理。
铁匠铺(含钩子)

public abstract class BlackSmith {final void make(){fire();if (work()){putMaterial();hit();}}void fire(){System.out.println("锻造炉升温");}abstract void putMaterial();void hit(){System.out.println("开始制作");}boolean work(){return true;}
}

凛冬将至

public class WinterIsComing extends BlackSmith{@Overridevoid putMaterial() {}@Overrideboolean work() {return false;}
}

在这里插入图片描述


IOC源码中模板方法模式的应用

相信刷过Java八股的同学在刚刚读到钩子方法的时候就已经想到了IOC容器初始化的实现了,各种论坛、文档上只要一提IOC过程、容器初始化 必然会说到钩子方法,但是相信很多同学也和笔者起初一样,对这种生涩的描述感觉一头雾水。今天我就带大家把这个听起来很晦涩的描述讲通。
没错,作为设计模式的集大成者,springframework在IOC的源码实现中包含着大量的钩子函数实现,这里也是对模板方法模式的主要应用。
我们先来看框架结构:
在这里插入图片描述
spring源码中,上下文接口ApplicationContext是很靠近底层的一个接口,它也是很重要的一个衔接接口。ConfigurableApplicationContext接口作为ApplicaitonContext的一个分支实现,承担了大量的springIOC初始化工作,而ConfigurableApplicationContext有一个核心的实现类AbstractApplicationContext,在这个实现类中有一个很重要很重要很重要的方法,就是refresh()方法
我愿意称这个方法为spring之梦开始的地方,这个方法就是模板方法模式的典型应用,包含了各种前置后置实现和钩子方法,更是spring生命周期的核心体现。
相关源码

	@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 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);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);beanPostProcess.end();// 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();contextRefresh.end();}}}

在这个方法中,postProcessBeanFactory,onRefresh都是预留的钩子方法,在这里都是空实现。而对于这些钩子的实现往往依赖于更高层的子类,比如说:GenericWebApplicationContext。
相关源码

	/*** Modify the application context's internal bean factory after its standard* initialization. All bean definitions will have been loaded, but no beans* will have been instantiated yet. This allows for registering special* BeanPostProcessors etc in certain ApplicationContext implementations.* @param beanFactory the bean factory used by the application context*/protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {}
	/*** Template method which can be overridden to add context-specific refresh work.* Called on initialization of special beans, before instantiation of singletons.* <p>This implementation is empty.* @throws BeansException in case of errors* @see #refresh()*/protected void onRefresh() throws BeansException {// For subclasses: do nothing by default.}
	/*** Register request/session scopes, environment beans, a {@link ServletContextAwareProcessor}, etc.*/@Overrideprotected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {if (this.servletContext != null) {beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext));beanFactory.ignoreDependencyInterface(ServletContextAware.class);}WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext);}//.../*** Initialize the theme capability.*/@Overrideprotected void onRefresh() {this.themeSource = UiApplicationContextUtils.initThemeSource(this);}

GenericWebApplicationContext对两个钩子写了具体的实现并根据自己的需求在refresh时按部就班的触发,形成了类似"子——父——子"的调用关系,这就是模板方法模式的魅力所在。
怎么样,相信大家看到这里会发现其实源码并没有那么晦涩,一些听到就打怵的词汇也没有那么高深。感兴趣的同学可以根据我的描述自己再追溯一下上述源码深度理解下。


模板方法模式的基本思想其实还是想让算法只存在于一处,这样更方便修改。本质上还是为了实现最大化的代码复用。就像我们开始说的PPT模板一样,父类模板方法实现的某些步骤可以直接被子类拿来使用。
这样既统一了算法,也提供了很大的灵活性。父类模板结构稳定,子类实现花样百出。
不过这种设计模式也存在着短板,就是可能引发类爆炸的问题,每一个不同的实现都需要一个子类,系统会逐渐变得笨重。所以一般模板方法都会加上final,防止子类重写模板方法。


关注我,共同进步,每天进步一点点。——Wayne

这篇关于19.严丝合缝的文明——模板方法模式详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/842626

相关文章

HTML5的input标签的`type`属性值详解和代码示例

《HTML5的input标签的`type`属性值详解和代码示例》HTML5的`input`标签提供了多种`type`属性值,用于创建不同类型的输入控件,满足用户输入的多样化需求,从文本输入、密码输入、... 目录一、引言二、文本类输入类型2.1 text2.2 password2.3 textarea(严格

检查 Nginx 是否启动的几种方法

《检查Nginx是否启动的几种方法》本文主要介绍了检查Nginx是否启动的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1. 使用 systemctl 命令(推荐)2. 使用 service 命令3. 检查进程是否存在4

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

MySQL字符串转数值的方法全解析

《MySQL字符串转数值的方法全解析》在MySQL开发中,字符串与数值的转换是高频操作,本文从隐式转换原理、显式转换方法、典型场景案例、风险防控四个维度系统梳理,助您精准掌握这一核心技能,需要的朋友可... 目录一、隐式转换:自动但需警惕的&ld编程quo;双刃剑”二、显式转换:三大核心方法详解三、典型场景

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程