Spring源码中的模板方法模式

news2024/9/23 15:24:00

1. 什么是模板方法模式

模板方法模式(Template Method Pattern)是一种行为设计模式,它在操作中定义算法的框架,将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。
在这里插入图片描述

模板方法模式的定义:

在操作中定义算法的框架,并将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。

模板方法中的算法可以理解为广义上的业务逻辑,并不是特指某一个实际的算法。定义中所说的算法的框架就是模板,包含算法框架的方法就是模板方法。
在这里插入图片描述

模板方法模式包含以下主要角色:

  • 抽象父类:定义一个算法所包含的所有步骤,并提供一些通用的方法逻辑。
  • 具体子类:继承自抽象父类,根据需要重写父类提供的算法步骤中的某些步骤。

抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

  • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

  • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

    • 抽象方法(Abstract Method):一个抽象方法由抽象类声明、由其具体子类实现。
    • 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
    • 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

钩子:在模板方法的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。钩子方法一般是空的或者有默认实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩,又由子类去决定。

2. 代码示例

/**
 * 抽象父类
 */
public abstract class AbstractClassTemplate {

    void step1(String key) {
        System.out.println("在模板类中 -> 执行步骤1");
        if (step2(key)) {
            step3();
        } else {
            step4();
        }
        step5();
    }

    boolean step2(String key) {
        System.out.println("在模板类中 -> 执行步骤2");
        return "x".equals(key);
    }

    abstract void step3();
    abstract void step4();

    void step5() {
        System.out.println("在模板类中 -> 执行步骤5");
    }

    void run(String key) {
        step1(key);
    }
}

public class ConcreteClassA extends AbstractClassTemplate {

    @Override
    void step3() {
        System.out.println("在子类A中 -> 执行步骤 3");
    }

    @Override
    void step4() {
        System.out.println("在子类A中 -> 执行步骤 4");
    }
}

public class ConcreteClassB extends AbstractClassTemplate {

    @Override
    void step3() {
        System.out.println("在子类B中 -> 执行步骤 3");
    }

    @Override
    void step4() {
        System.out.println("在子类B中 -> 执行步骤 4");
    }
}

public class Test01 {

    public static void main(String[] args) {
        AbstractClassTemplate concreteClassA = new ConcreteClassA();
        concreteClassA.run("");

        System.out.println("===========");

        AbstractClassTemplate concreteClassB = new ConcreteClassB();
        concreteClassB.run("x");
    }
}

// 输出结果
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类A-> 执行步骤 4
在模板类中 -> 执行步骤5
===========
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类B-> 执行步骤 3
在模板类中 -> 执行步骤5
3. JdbcTemplate应用模板方法模式

在原生JDBC操作中,需要执行以下步骤:

  1. 获取connection
  2. 获取statement
  3. 获取resultset
  4. 遍历resultset并封装成集合
  5. 依次关闭connection, statement, resultset,并考虑各种异常

上面步骤中大多数都是重复的、可复用的,只有在遍历ResultSet并封装成集合的步骤是可定制的。每张表都映射不同的Java bean,这部分代码是没有办法复用的,只能定制。

// 模板方法,用来执行 JDBC 操作,返回结果集或受影响的行数
protected <T> T execute(ConnectionCallback<T> action, boolean enforceReadOnly) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");

    Connection con = DataSourceUtils.getConnection(obtainDataSource());
    try {
        boolean readOnly = enforceReadOnly || isReadOnly();
        // 设置是否为只读连接
        prepareConnection(con, readOnly);
        // 执行具体的 JDBC 操作,该方法为子类实现
        T result = action.doInConnection(con);
        // 提交事务
        DataSourceUtils.commitIfNecessary(con, getDataSource());
        // 返回结果集或受影响的行数
        return result;
    } catch (SQLException ex) {
        // 回滚事务
        DataSourceUtils.rollbackIfNecessary(con, getDataSource());
        throw translateException("Callback", getSql(action), ex);
    } finally {
        DataSourceUtils.releaseConnection(con, getDataSource());
    }
}

// 执行给定的 SQL 语句和参数,返回查询结果
public <T> T query(final String sql, final ResultSetExtractor<T> rse, Object... args) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");

    // 匿名内部类,实现 ConnectionCallback 接口
    return execute(new ConnectionCallback<T>() {
        @Override
        public T doInConnection(Connection con) throws SQLException {
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 创建 PreparedStatement 对象
                ps = createPreparedStatement(con, sql);
                // 设置 PreparedStatement 的参数
                setValues(ps, args);
                // 执行查询,返回结果集
                rs = ps.executeQuery();
                // 对结果集进行处理,返回查询结果
                return rse.extractData(rs);
            } finally {
                JdbcUtils.closeResultSet(rs);
                JdbcUtils.closeStatement(ps);
            }
        }
    }, true);
}

4. 运用模板方法手写简单版JdbcTemplate

  1. 定义DefineJcbcTemplate自定义jdbcTemplate接口
public interface DefineJcbcTemplate {
    <T> T queryForObject(String sql, DefineRowMapper<T> defineRowMapper);
}
  1. 定义DefineRowMapper函数式接口,回调作用,处理jdbc查询结果ResultSet,返回泛型T对象
@FunctionalInterface
public interface DefineRowMapper<T> {
    T mapRow(ResultSet rs) throws SQLException;
}
  1. DefineJcbcTemplateImpl实现类,该实现类的连接数据库、释放资源等操作都是从步骤一复制粘贴进来的,唯一不同的是把处理结果解耦出来了,定义好处理结果的接口DefineRowMapper,交由调用者去实现对结果的处理,最后回调该接口的方法mapRow并返回结果,其他的把连接数据库、释放资源封装起来,这样一来不用每次进行数据库查询都需要连接数据库、释放资源。
@Service
public class DefineJcbcTemplateImpl implements DefineJcbcTemplate {

    @Autowired
    private DataSource dataSource;

    @Override
    public <T> T queryForObject(String sql, DefineRowMapper<T> defineRowMapper) {
        // 一部分是准备和释放资源以及执行 SQL 语句,另一部分则是处理 SQL 执行结果
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 创建dataSource,获取连接
            connection = dataSource.getConnection();
            // 执行查询
            preparedStatement = connection.prepareStatement(sql);
            // 获取执行结果
            resultSet = preparedStatement.executeQuery();
            // 交由调用者去实现对结果的处理
            return defineRowMapper.mapRow(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (preparedStatement != null) {
                try {
                    preparedStatement.close

();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return null;
    }
}
  1. 调用自定义模板方法进行数据库查询
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestSpringTemplateApplication.class)
public class TestSpringTemplateApplicationTests {

    @Autowired
    private DefineJcbcTemplate defineJcbcTemplate;

    @Test
    public void testQueryForObject() {
        // 定义sql
        String sql = "SELECT * FROM user";
        // 通过模版方法进行查询
        List<User> users = defineJcbcTemplate.queryForObject(sql, (rs) -> {
            List<User> result = new ArrayList<>();
            while (rs.next()) {
                // 遍历ResultSet并封装成集合
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setEmail(rs.getString("email"));
                result.add(user);
            }
            return result;
        });
        System.out.println(users);
    }
}

通过上面的代码,您可以看到模板方法模式可以很容易地将重复的部分代码提取到父类中,而将可变的部分代码放入子类中,从而实现代码的复用和扩展。

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

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

相关文章

Linux内核编译安装 - Deepin,Debian系

为什么要自己编译内核 优点 定制化&#xff1a;你可以根据自己的硬件和需求配置内核&#xff0c;去掉不必要的模块&#xff0c;优化性能。性能优化&#xff1a;移除不需要的驱动程序和特性&#xff0c;减小内核体积&#xff0c;提高系统性能。最新特性和修复&#xff1a;获取…

网络(二)——套接字编程

文章目录 理解源IP地址和目的IP地址认识端口号认识TCP/UDP协议网络字节序socket编程接口socket 常见APIsockaddr结构 理解源IP地址和目的IP地址 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址&#xff1b; 源IP即发送方的地址&#xff0c;目的IP即接受方的…

[译] Rust标准库有些特殊,让我们改它

本篇是对 RustConf 2023中的The standard library is special. Let’s change that.这一视频的翻译与整理, 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. 今天我将讨论Rust的标准库,更具体地说,是关于标准库有何特殊之处,以及为什么我们应该改变这一点。首先声明一下…

探索 Prompt 的世界:让你的 AI 更智能

探索 Prompt 的世界&#xff1a;让你的 AI 更智能 引言什么是 Prompt&#xff1f;Prompt 的重要性如何编写有效的 Prompt1. 清晰明确2. 包含关键细节3. 提供上下文 实践中的 Prompt 技巧1. 多次迭代2. 实验不同风格3. 结合实际应用 总结 引言 随着人工智能&#xff08;AI&…

通过vm可以访问那些属性——06

1.通过vue实例都可以访问那些属性&#xff1f;&#xff08;通过vm都可以vm.什么&#xff09; vue实例中的属性很多。有的以$开始&#xff0c;有的以_开始。 所有以$开始的属性&#xff0c;可以看做是公开的属性&#xff0c;这些属性是提供给程序员使用的 所有以_开始的属性&…

PyTorch是使用GPU和CPU优化的深度学习张量库——torchvision

torchvision datasets torchvision.datasets 包含了许多标准数据集的加载器。例如&#xff0c;CIFAR10 和 ImageFolder 是其中两个非常常用的类。 CIFAR10 CIFAR10 数据集是一个广泛使用的数据集&#xff0c;包含10类彩色图像&#xff0c;每类有6000张图像&#xff08;5000张…

<数据集>夜间车辆识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;5000张 标注数量(xml文件个数)&#xff1a;5000 标注数量(txt文件个数)&#xff1a;5000 标注类别数&#xff1a;8 标注类别名称&#xff1a;[car, pedestrian, traffic light, traffic sign, bicycle, bus, truck…

Leetcode(经典题)day2

H指数 274. H 指数 - 力扣&#xff08;LeetCode&#xff09; 先对数组排序&#xff0c;然后从大的一头开始遍历&#xff0c;只要数组当前的数比现在的h指数大就给h指数1&#xff0c;直到数组当前的数比现在的h指数小的时候结束&#xff0c;这时h的值就是要返回的结果。 排序…

Ubuntu搭建Android架构so库交叉编译环境

目录 前言一、下载NDK并安装二、安装NDK三、配置交叉编译工具链四、编写交叉编译脚本 前言 需要将一些源码编译成Android可用的架构的so库 一、下载NDK并安装 https://developer.android.google.cn/ndk/downloads/ 二、安装NDK 将下载下来的android-ndk-r23b-linux.zip解压…

17099 周工作计划安排

这个问题可以通过动态规划来解决。我们可以定义一个数组d&#xff0c;其中d[i]表示第i周选择项目后&#xff0c;产生的最大效益和。然后我们可以通过比较选择低压项目和高压项目的效益&#xff0c;来更新d[i]。 以下是解题步骤&#xff1a; 1. 初始化数组&#xff1a;首先&am…

《Linux系统编程篇》认识在linux上的文件 ——基础篇

前言 Linux系统编程的文件操作如同掌握了一把魔法钥匙&#xff0c;打开了无尽可能性的大门。在这个世界中&#xff0c;你需要了解文件描述符、文件权限、文件路径等基础知识&#xff0c;就像探险家需要了解地图和指南针一样。而了解这些基础知识&#xff0c;就像学会了魔法咒语…

视频播放器的问题

<template><div class"app-container"><el-form :model"queryParam" ref"queryForm" :inline"true"><el-form-item label"题目ID&#xff1a;"><el-input v-model"queryParam.id" cle…

python:绘制一元三次函数的曲线

编写 test_x3_3x.py 如下 # -*- coding: utf-8 -*- """ 绘制函数 y x^33x4 在 -3<x<3 的曲线 """ import numpy as np from matplotlib import pyplot as plt# 用于正常显示中文标题&#xff0c;负号 plt.rcParams[font.sans-serif] […

免费的AI抠图工具 毫秒级抠图 离线可用 -鲜艺AI抠图

鲜艺AI抠图是一款免费的AI抠图工具&#xff0c;不登录、不联网&#xff0c;内嵌 AI 模型&#xff0c;快至毫秒级抠图&#xff0c;支持批量抠图&#xff0c;支持点击按钮选择图片、拖入图片、粘贴图片、粘贴图片链接、从网页拖入图片&#xff0c;支持Windows和macos&#xff0c;…

Linux:Linux网络总结(附下载链接)

文章目录 下载链接网络问题综合问题访问一个网页的全过程&#xff1f;WebSocket HTTPHTTP基本概念GET与POSTHTTP特性HTTP缓存技术HTTP的演变HTTP1.1 优化 HTTPSHTTP与HTTPS有哪些区别&#xff1f;HTTPS解决了HTTP的哪些问题&#xff1f;HTTPS如何解决的&#xff1f;HTTPS是如何…

【触想智能】安卓工控一体机在自助终端设备上的应用分析

随着科技的发展和人们对自动化系统的需求不断增强&#xff0c;自助终端设备已经成为日常生活非常常见的设备之一&#xff0c;例如自助售货机、自助点餐机、自助银行服务等。这些设备在使用中都需要一个可靠的、稳定的操作系统来支持其各项功能的实现。 因此&#xff0c;安卓工控…

Postman接口模拟请求工具使用技巧

Postman是一款非常强大的接口模拟请求工具&#xff0c;可以帮助开发者快速测试、调试API接口。下面集合实际使用过程中的经验&#xff0c;分享大家一些基础使用技巧&#xff1a; 1. 安装与启动&#xff1a;首先在官网&#xff08;Download Postman | Get Started for Free&…

Qt下使用OpenCV的鼠标回调函数进行圆形/矩形/多边形的绘制

文章目录 前言一、设置imshow显示窗口二、绘制圆形三、绘制矩形四、绘制多边形五、示例完整代码总结 前言 本文主要讲述了在Qt下使用OpenCV的鼠标回调在OpenCV的namedWindow和imshow函数显示出来的界面上进行一些图形的绘制&#xff0c;并最终将绘制好的图形显示在QLabel上。示…

html(抽奖设计)

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>抽奖</title><style type"text/css">* {margin: 0;padding: 0;}.container {width: 800px;height: 800px;border: 1px dashed red;position: absolut…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(四)-无人机系统(UAS)命令与控制(C2)通信用例

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…