【设计模式】模板方法模式--让你的代码更具灵活性与可扩展性

news2024/11/19 18:39:41

文章目录

  • 前言
  • 模板方法模式的定义
    • 核心组成
    • 模板方法模式与其他设计模式的区别
  • 代码实现
    • 抽象类
    • 具体类
    • Client
  • 经典类图
    • spring中的例子
  • 总结

前言

在软件开发中,设计模式是一种经过实践检验的、可复用的解决方案,它们可以帮助我们解决某一特定领域的典型问题。设计模式不仅能提高代码的可读性、可维护性,还能让我们的代码更加灵活和易于扩展。在这个不断发展的技术世界中,了解并掌握设计模式对于软件开发人员来说是至关重要的。

模板方法模式是一种广泛应用于各种软件系统中的行为设计模式。它在算法的骨架上定义了一系列步骤,而具体的实现则留给子类来完成。通过这种方式,模板方法模式允许我们在不改变算法结构的情况下,灵活地重用和扩展代码。

  • 在这篇文章中,我们将深入探讨模板方法模式的定义、工作原理和适用场景。
  • 我们还将通过一个实际的代码示例,来展示如何在我们熟悉的编程语言中实现模板方法模式。
  • 最后,我们将讨论模板方法模式的优缺点,以帮助你在实际开发中做出明智的决策。

模板方法模式的定义

模板方法模式是一种行为设计模式,它在父类中定义了一个算法的骨架,将一些具体步骤的实现延迟到子类。这样,模板方法模式允许子类在不改变算法结构的情况下,重新定义算法中的某些步骤。这是“设计模式:可复用面向对象软件的基础”一书中对模板方法模式的正式定义。

用通俗易懂的话来说,模板方法模式就像是一份食谱。食谱提供了烹饪某道菜的基本步骤,但具体的烹饪方法和食材可以根据个人口味进行调整。

在编程中,我们可以将这些基本步骤定义在一个抽象类(食谱)中,然后让子类(具体的烹饪方式)根据需要重写或扩展这些步骤。

核心组成

模板方法模式的核心组成部分包括:

  • 抽象类:定义了一个算法的骨架,包含一系列抽象方法和具体方法。抽象方法由子类实现,具体方法可以包含一些通用逻辑或者调用抽象方法。
  • 具体类:继承自抽象类,实现抽象方法,根据需要重写或扩展算法中的具体步骤。

通过这种方式,模板方法模式让我们能够在保持算法骨架不变的前提下,根据实际需求灵活地调整和扩展算法的具体实现。

模板方法模式与其他设计模式的区别

  1. 模板方法模式 vs 策略模式: 模板方法模式主要关注算法的骨架和步骤,通过继承抽象类来扩展或重写算法的部分步骤。而策略模式关注于将算法封装成一组可互换的策略对象,通过组合来实现不同的算法。模板方法模式使用继承,策略模式使用组合。

  2. 模板方法模式 vs 工厂方法模式: 模板方法模式关注的是算法的骨架和不同的实现细节。工厂方法模式则是一种创建型设计模式,关注的是如何创建对象,将对象的实例化延迟到子类中实现。它们都利用继承来实现不同的行为,但解决的问题类型不同。

  3. 模板方法模式 vs 装饰器模式: 模板方法模式通过继承抽象类来重写或扩展算法的部分步骤。装饰器模式则通过将对象包装在装饰器类中来动态地扩展对象的功能。装饰器模式不改变原对象的接口,而是通过组合来实现功能的扩展。模板方法模式侧重于算法的骨架,装饰器模式侧重于对象功能的动态扩展。

  4. 模板方法模式 vs 观察者模式: 模板方法模式关注算法的骨架和部分实现细节。观察者模式则是一种行为型设计模式,主要用于实现对象之间的松耦合通信。观察者模式中,被观察者(主题)维护一组观察者,并在状态改变时通知它们。模板方法模式和观察者模式解决的问题类型和目的不同。

代码实现

咖啡店中不同类型的咖啡制作过程。假设我们有一个咖啡店,出售美式咖啡和拿铁咖啡。这两种咖啡的制作过程有些相似,但也有些不同。我们可以使用模板方法模式来实现这个场景。

抽象类

// 抽象类:CoffeeBeverage,定义咖啡制作的骨架
abstract class CoffeeBeverage {
    // 模板方法:prepareCoffee,定义制作咖啡的步骤
    public final void prepareCoffee() {
        boilWater();
        brewCoffee();
        pourInCup();
        addCondiments();
    }

    // 具体方法:boilWater,烧水(公共步骤)
    private void boilWater() {
        System.out.println("Boiling water");
    }

    // 抽象方法:brewCoffee,冲泡咖啡(留给子类实现)
    protected abstract void brewCoffee();

    // 具体方法:pourInCup,倒进杯子(公共步骤)
    private void pourInCup() {
        System.out.println("Pouring coffee into the cup");
    }

    // 抽象方法:addCondiments,加入调料(留给子类实现)
    protected abstract void addCondiments();
}

具体类

// 具体类:Americano,美式咖啡
class Americano extends CoffeeBeverage {
    @Override
    protected void brewCoffee() {
        System.out.println("Brewing Americano coffee");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

// 具体类:Latte,拿铁咖啡
class Latte extends CoffeeBeverage {
    @Override
    protected void brewCoffee() {
        System.out.println("Brewing Latte coffee");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding steamed milk and foam");
    }
}

Client

// 测试类
public class CoffeeShop {
    public static void main(String[] args) {
        System.out.println("Making an Americano coffee:");
        CoffeeBeverage americano = new Americano();
        americano.prepareCoffee();

        System.out.println("\nMaking a Latte coffee:");
        CoffeeBeverage latte = new Latte();
        latte.prepareCoffee();
    }
}

代码解释:

  • 首先,我们创建了一个名为 CoffeeBeverage 的抽象类,它定义了制作咖啡的骨架。其中,prepareCoffee() 方法是一个模板方法,它定义了咖啡制作的步骤。boilWater() 和 pourInCup() 方法是具体方法,表示公共步骤,而 brewCoffee() 和 addCondiments() 方法是抽象方法,留给子类实现。

  • 接下来,我们创建了两个具体类:Americano 和 Latte,分别表示美式咖啡和拿铁咖啡。这两个类继承了 CoffeeBeverage 抽象类,并分别实现了 brewCoffee() 和 addCondiments() 方法。这样,我们就为每种咖啡定义了各自的制作细节。

  • 最后,我们创建了一个测试类 CoffeeShop,在 main() 方法中,我们实例化了 Americano 和 Latte 类,然后分别调用它们的 prepareCoffee() 方法来制作咖啡。运行程序后,你将看到每种咖啡制作过程中的不同步骤。

经典类图

在这里插入图片描述

spring中的例子

Spring 框架中,有一个典型的应用模板方法模式的例子,那就是 JdbcTemplate 类。JdbcTemplateSpring 框架为简化 JDBC 操作而提供的一个工具类,它通过模板方法模式来处理与数据库相关的一些通用操作,例如资源管理、错误处理等。JdbcTemplate 提供了一系列模板方法,如 query(), update() 等,允许开发者仅关注 SQL 语句及结果集的处理,而无需关心底层资源的分配与释放等问题。
下面是一个简单的例子,说明如何使用 Spring 的 JdbcTemplate 类来查询数据库中的数据:

首先,我们需要在项目中引入 Spring JDBC 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

接下来,创建一个 User 实体类,用于表示数据库中的用户信息:

@Data
public class User {
    private int id;
    private String name;
    private String email;
}

然后,创建一个 UserDao 接口,定义查询用户的方法:

public interface UserDao {
    List<User> findAllUsers();
}

创建 UserDaoImpl 类,实现 UserDao 接口,并使用 JdbcTemplate 查询数据库:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;

    public UserDaoImpl(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public List<User> findAllUsers() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, new UserRowMapper());
    }

    private static final class UserRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet resultSet, int rowNum) throws SQLException {
            User user = new User();
            user.setId(resultSet.getInt("id"));
            user.setName(resultSet.getString("name"));
            user.setEmail(resultSet.getString("email"));
            return user;
        }
    }
}

在这个例子中,我们使用了 JdbcTemplate 的 query() 方法作为模板方法,它接受一个 SQL 语句和一个自定义的 RowMapper 作为参数。我们只需要实现 RowMapper 的 mapRow() 方法来处理结果集,而无需关心其他底层操作。这正是模板方法模式的优势所在。

总之,Spring 框架中的 JdbcTemplate 是一个很好的模板方法模式实践例子。它简化了数据库操作,使得开发者可以专注于 SQL 语句和结果集处理,而无需关心底层的资源管理和错误处理等问题。

总结

  • 优点:

    • 代码复用:模板方法模式将公共的行为提取到父类中,子类只需实现特定的行为。这有助于减少代码重复,并提高了代码的可维护性。
    • 提高代码的可扩展性:模板方法模式允许在不修改抽象类的前提下,扩展和修改子类的具体行为。这意味着我们可以轻松地添加新的子类以实现新的功能。
    • 封装性良好:模板方法模式将算法的实现细节封装在子类中,父类只需关心算法的整体结构。这有助于将关注点分离,使得代码更容易理解和维护。
    • 便于维护:模板方法模式将公共逻辑集中在父类中,使得维护和修改这些逻辑变得更加简单。此外,对于子类的修改,也可以方便地进行定位和维护。
  • 缺点:

    • 类数量可能增多:由于模板方法模式需要为每个具体实现创建一个子类,这可能导致类数量的增加,从而增加了系统的复杂性。
    • 可能导致过度抽象:在设计模板方法时,如果过度抽象,可能导致子类的实现变得复杂和难以理解。因此,在设计时需要权衡抽象层次和实际需求。
    • 不同子类之间的代码重复:如果子类之间存在相似的行为,可能导致代码重复。在这种情况下,可以通过进一步提取公共代码到父类中或使用其他设计模式来解决这个问题。

总之,模板方法模式是一种非常实用的设计模式,它可以帮助我们在保持代码结构的同时,实现灵活的行为扩展。然而,使用模板方法模式时,需要注意避免类数量的过度增长和过度抽象的问题。

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

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

相关文章

【2023 · CANN训练营第一季】应用开发深入讲解——第一章 模型转换(1)

学习目标 学习资源 1.模型转换 模型转换基础 在线课程 文档AIPP功能说明 文档 应用开发&#xff08;初级&#xff09; 课程目标&#xff1a; 了解AscendCL的使用场景、基本概念、基本开发流程。 学会使用AscendCL接口开发基础推理应用&#xff0c;应用中包含对图片的基本处理…

javaScript常见面试题(二)

一、浅拷贝和深拷贝 浅拷贝&#xff1a;&#xff08;1&#xff09;对于基本数据类型&#xff0c;拷贝的是值&#xff0c;修改的时候数据互不影响&#xff1b;&#xff08;2&#xff09;对于引用数据类型&#xff0c;拷贝的是在堆内存中存储的内存地址&#xff0c;修改的时候&a…

mapreduce基础: 手写本地wordcount案例

文章目录 一、源代码1. WordCountMapper类2. WordCountReducer类3. WordCountDriver类 二、运行截图 一、源代码 1. WordCountMapper类 package org.example.wordcount;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apac…

yum库和nfs共享服务

yum库 redhat&#xff0c;centos用的是.rpm的包&#xff0c;用yum解决依赖包关系下载 ubuntu&#xff0c;debian用的是.deb的包&#xff0c;用apt解决依赖包的关系下载 yum软件仓库的提供方式 FTP服务: ftp://… HTTP服务: http://…或者https&#xff1a;//… 本地目录: fi…

母婴品牌内容输出怎么做?“四板斧”送你

新媒体时代&#xff0c;信息大爆炸&#xff0c;人们的注意力有限&#xff0c;有噱头和亮点的内容才能博得注意&#xff0c;成为用户关注的焦点。 母婴行业重视品牌效益和产品的质量&#xff0c;毕竟类似“三聚氰胺”的惨剧谁也不希望再发生。母婴产品的质量依赖技术和生产线支…

C语言从入门到精通第9天(循环结构的使用)

循环结构的使用 while语句do-while语句for语句嵌套循环 循环结构可以重复的执行一段代码块&#xff0c;在C语言中提供了三种不同类型的循环结构&#xff1a;for、while和do-while。 while语句 语法&#xff1a; while(表达式){ 语句&#xff1b; } 如果表达式为真则执行结构体…

并发编程学习笔记

为什么学&#xff1f; 只要去做一个技术含量稍微好点的系统&#xff0c;并发包下面的东西是很容易会要用到的 synchronized(Object){ } 主要是用来给对象加锁 synchronized底层原理 首先监视器的计数器是0&#xff0c;然后线程一访问&#xff0c;会将值改为1&#xff0c;…

[Gitops--4] OpenELB

OpenELB OpenELB是一个开源的负载均衡器,功能和metalLB类似 OpenELB主要两种工作模式: Layer2和BGP模式.目前OpenELB的BGP不支持ipv6 OpenELB核心思想就是通过某种方式将特定的VIP的流量引导k8s集群中,然后通过Kube-proxy将流量转发到后面的特定服务. 1. OpenELB介绍 1.1 La…

对于python文件,敲下回车后发生了什么

引言 我们常说 Python 一是门解释型语言&#xff0c;只需要敲下 python code.py就可以运行编写的代码&#xff0c;而无需使用类似于 javac 或者 gcc 进行编译。那么&#xff0c;Python 解释器是真的一行一行读取 Python 源代码而后执行吗? 实际上&#xff0c;Python 在执行程序…

为什么要进行倾斜摄影三维模型的顶层合并?

为什么要进行倾斜摄影三维模型的顶层合并&#xff1f; 1、倾斜摄影三维模型顶层合并的重要性 倾斜摄影三维模型的顶层合并是指将拍摄同一区域的多个倾斜角度的影像进行融合&#xff0c;生成一个连续的、完整的三维地理信息数据。其原因主要有以下几点&#xff1a; &#xff0…

关于倾斜摄影三维模型轻量化数据大小和质量关系分析

关于倾斜摄影三维模型轻量化数据大小和质量关系分析 倾斜摄影三维模型轻量化是一种常用的技术&#xff0c;通过对原始三维模型数据进行压缩和简化&#xff0c;减小其数据大小&#xff0c;从而提高数据传输和展示效率。然而&#xff0c;轻量化过程中可能会对数据质量产生影响。以…

性能测试——安装Loadrunner11.0的详细步骤

一、下载Loadrunner11.0版本 去相关网站下载即可 二、安装 &#xff08;windows与虚拟机上安装操作大相径庭&#xff09; 1、将ISO文件导入&#xff0c;打开光驱&#xff0c;运行“setup.exe“ 2、点击安装&#xff0c;部分机器会提示缺少“Microsoft Visual C 2005 SP1运行组…

有效的字母异位词

给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetco…

Java 将json中key值中带有下划线的部分转为驼峰格式

一、背景说明 在开发过程中&#xff0c;有时会遇到第三方厂商提供的接口返回结果不是严格按照驼峰命名&#xff0c;需要将其中带有下划线的字段进行格式化转换为驼峰命名。 如下图中的self_auth、user_id、user_name、creator_name 和 others_auths 等 key 值。 如果是对 JS…

Mysql 45讲和45问笔记(未完待续0203/04/24)

一、mysql 45讲 1&#xff09;索引的本质讲解 定义解释 所以是帮助Mysql高效获取数据的排好序的数据结构 索引数据结构 ①二叉树 ②红黑树 ③Hash表 ④B-Tree 原理讲解 可以看到右边的数据结构里面&#xff0c;是按照k-v来存数据结构的&#xff0c;key是col2的字段&#xf…

【Linux】线程-线程概念

线程概念 什么是线程线程的优点和缺点线程的用途和线程异常线程与进程的区别 什么是线程 实际上&#xff0c;线程是一个进程内部的控制序列&#xff0c;一个程序的一个执行线路就是一个线程。 并且一个进程中至少有一个线程&#xff0c;本质上&#xff0c;一个进程内部如果有多…

6. 树的入门

6. 树的入门 之前我们实现的符号表中&#xff0c;不难看出&#xff0c;符号表的增删查操作&#xff0c;随着元素个数N的增多&#xff0c;其耗时也是线性增多的&#xff0c;时间复杂度都是O(n),为了提高运算效率&#xff0c;接下来我们学习树这种数据结构。 6.1 树的基本定义 …

MybatisPlus-入门项目搭建、SQL日志打印、实体类注解、简单持久层操作

Mybatis-Plus mybatis plus概述 ​ Mybatis Plus &#xff08;opens new window&#xff09;简称 MP&#xff0c;它是一个MyBatis 的增加工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 Mybatis Plus官网&#xff1a;https://…

拉格朗日函数对偶问题、KKT条件

一、概念介绍 KKT最优化条件是Karush(1939)以及Kuhn和Tucker(1951)先后独立发表出来的&#xff0c;但在Kuhn和Tucker发表之后才逐渐受到重视&#xff0c;因此多数情况下记载成库恩-塔克条件(Kuhn-Tucker conditions)。先介绍几个优化的概念。 1.1 优化 最优化问题&#xff0…

大彩串口屏新品发布:大彩7寸新品RS485/232双通讯口人机界面发布!

一、产品介绍 新品发布&#xff1a;大彩7寸新品RS485/232双通讯口人机界面发布&#xff01; 此次发布7.0寸外壳HMI产品采用普清800*480液晶屏、触摸选用电阻触摸&#xff0c;硬件性能上与M型医用级组态串口屏一样&#xff0c;软件上拥有炫酷的动画效果&#xff0c;页面切换流…