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

news2024/11/13 7:55:24

在这里插入图片描述
“项目评审的节点又快到了,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{
    @Override
    void putMaterial() {
        System.out.println("放入铸剑材料:铁矿石");
    }
}

制弓室

public class ArchBlackSmith extends BlackSmith{
    @Override
    void 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{

    @Override
    void putMaterial() {}

    @Override
    boolean work() {
        return false;
    }
}

在这里插入图片描述


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

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

	@Override
	public 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.
	 */
	@Override
	protected 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.
	 */
	@Override
	protected void onRefresh() {
		this.themeSource = UiApplicationContextUtils.initThemeSource(this);
	}

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


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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1544213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言栈和队列(个人笔记)

栈和队列 栈1.1栈的概念和结构1.2栈的实现 队列2.1队列的概念及结构2.2队列的实现2.3循环队列 栈和队列笔试题3.1[有效的括号](https://leetcode.cn/problems/valid-parentheses/submissions/516297357/)3.2[用队列实现栈](https://leetcode.cn/problems/implement-stack-using…

HCIA实验

实验目的&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址&#xff0c;之后不能再对其进行任何配置&#xff1b; 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配&#xff1b; 3、R1、R2、R4&#x…

阿里云服务器一年多少钱?2024最新活动价格表整理分享

2024年阿里云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

C语言 青蛙跳台阶问题

1.问题描述 一只青蛙可以一次跳一级台阶&#xff0c;也可以一次跳两级台阶&#xff0c;如果青蛙要跳上n级台阶有多少种跳法&#xff1f; 2.问题分析 当台阶只有一级时&#xff0c;只能跳一级&#xff0c;所以只有一种跳法 当台阶有两级时&#xff0c;可以先跳一级&#xff…

HDFS集群环境配置

HDFS集群环境配置 环境如下三台服务器&#xff1a; 192.168.32.101 node1192.168.32.102 node2192.168.32.103 node3 一、Hadoop安装包下载​​​​​​​ 点此官网下载​​​​​​​ 二、Hadoop HDFS的角色包含&#xff1a; NameNode&#xff0c;主节点管理者DataNode&am…

kubernetes最小调度单元Pod概述

Pod概述 一.Pod的概念1.Pod是什么2.Pod网络共享实现方式3.Pod存储共享方式4.创建Pod的流程 二.使用YAML文件定义Pod资源1.Pod资源清单YAML文件书写技巧1.YAML语法格式&#xff1a;2.配置Linux tab缩进两个空格3.使用kubectl explain帮助命令 2.创建Pod及Pod常用命令1.创建Pod资…

MySQL-1.数据库的基本操作

1. 数据库的基本操作 show databases; information_schema&#xff1a;信息图式&#xff0c;存储服务器管理数据库的信息 mysql&#xff1a;存放系统信息&#xff0c;用户名密码等 performance_schema&#xff1a;性能图式 sys&#xff1a;系统文件 1.1 创建数据库-studen…

瑞吉外卖实战学习--项目搭建

瑞吉外卖实战学习 前言1、创建springBoot 项目&#xff0c;并引用相关依赖2、配置数据库3、通过注解检测项目是否可以启动成功4、配置前端页面的静态映射4.1 前端文件放置的位置4.2 由于存放的位置并不是默认的文件中&#xff0c;需要将这些文件静态映射4.3 检测静态文件是否可…

003- AutoCoder 使用Web版大模型,性感的Human As Model 模式

这是下面这篇文章的继续。 002- 用 AutoCoder 添加和修改代码 前面我们提到&#xff0c;如何解决你没有API版大模型&#xff0c;或者你的API版大模型太弱&#xff0c;而你只有Web版本的诸如 Kimi/GPT4 的情况下&#xff0c;改如何让AutoCoder帮助你完成编程&#xff1f; 我们有…

数据结构(五)——树与二叉树的应用

5.5 树与二叉树的应用 5.5.1 哈夫曼树 结点的权&#xff1a;有某种现实含义的数值。 结点的带权路径长度&#xff1a;从树的根到该结点的路径长度&#xff08;经过的边数&#xff09;与该结点上权值的乘积。 树的带权路径长度&#xff1a;树中所有叶结点的带权路径长度之和…

Linux——进程信号(二)

目录 1、阻塞信号 1.1、信号其他相关常见概念 1.2、在内核中的表示 1.3、sigset_t 1.4、信号集操作函数 2、捕捉信号 2.1、内核如何捕捉信号 5.2、sigaction 1、阻塞信号 1.1、信号其他相关常见概念 实际执行信号的处理动作被称为信号递达&#xff08;Delivery&#x…

电脑桌面便签,怎么在电脑桌面上设置便签

在数字化时代&#xff0c;电脑已成为我们日常生活不可或缺的一部分。在我们使用电脑进行各种工作和学习的过程中&#xff0c;经常会遇到需要记录临时信息或提醒自己的情况。这时&#xff0c;设置便签在电脑桌面上就成为了一种非常便捷的方法。那么有一个问题&#xff0c;电脑桌…

(一)基于IDEA的JAVA基础8

使用多重if选择结构 多个if条件进行判断: 语法: if(条件1){ 执行语句1&#xff1b; }else if(条件2){ 执行语句2&#xff1b; }else if(条件3){ 执行语句3&#xff1b; }else if (条件4)…… 流程图: 我们来写个好玩的&#xff0c;对暗号: public class Test01 { …

web前端之罗盘时钟、不一样的补零方式、LED字体、padStart

MENU 效果图htmlJavaScriptstyle 效果图 html <div class"clock"><div class"second-box"></div><div class"minute-box"></div><div class"hour-box"></div><div class"day-box&…

atoi函数的使用和模拟实现

1.atoi函数简介 (1).atoi函数原型 &#xff1a;int atoi (const char * str); (2).头文件&#xff1a;<stdlib.h> 用法&#xff1a;将字符串里的数字字符转化为整形数。返回整形值。 注意&#xff1a; 转化时跳过前面的空格字符&#xff0c;直到遇上数字或正负符号才开…

C++中的lambda表达式

引入: 首先来看一个例子 struct fruit {double _price;int _evalute;string _name;fruit(const char* str, int a, double price):_name(str),_evalute(a),_price(price){} }; struct ComparePriceGreater {bool operator()(const fruit& g1, const fruit& gr){return…

redis在docker安装并启动流程

1、启动server docker run -d -p 6379:6379 --name redis01 redis:7.2.4以上命令&#xff0c;每次启动新的Redis容器&#xff0c;数据会丢失。 我们需要挂载数据文件&#xff0c;在宿主机上面&#xff0c;这样就可以持久化数据. 2、挂载数据文件&#xff08;可根据需求选择…

Linux常用操作命令(清单快查版)

Linux常用操作命令&#xff0c;今日先给出快查清单&#xff0c;后续出带命令参数及不同OS的区别语法的相关示例 1. 文件与目录操作 命令描述ls列出目录内容cd切换目录pwd显示当前工作目录mkdir创建目录rmdir删除空目录cp复制文件或目录mv移动或重命名文件或目录rm删除文件或目…

(异步编程)前端八股文修炼Day3

一异步编程异步编程的实现方式&#xff1f; 在 JavaScript 中&#xff0c;异步编程是处理异步操作的重要部分&#xff0c;常见的异步编程实现方式有以下几种&#xff1a; 回调函数&#xff08;Callbacks&#xff09;&#xff1a;回调函数是最基本的异步编程方式&#xff0c;通…

【第三方登录】Google邮箱

登录谷歌邮箱开发者 https://console.developers.google.com/ 先创建项目 我们用的web应用 设置回调 核心主要&#xff1a; 1.创建应用 2.创建客户端ID 3.设置域名和重定向URL 4.对外公开&#xff0c;这样所有的gmail邮箱 都能参与测试PHP代码实现 引入第三方包 h…