设计模式-模板方法模式(TemplateMethod)

news2024/9/22 15:50:04

1. 概念

  • 模板方法模式是一种行为设计模式,它在一个方法中定义算法的骨架,将一些步骤延迟到子类中实现。

2. 原理结构图

2.1 图

在这里插入图片描述

2.2 角色
  • 抽象类(Abstract Class)
    • 定义抽象的基本操作(Primitive Operations),这些操作构成了模板方法的骨架。
    • 实现模板方法(Template Method),模板方法是定义算法结构的方法,它调用基本操作来执行这个算法的步骤。
    • 可以定义一些钩子(Hook),这些是抽象方法或具体方法,提供了默认实现,子类可以选择性地覆盖它们。
      • 具体方法(Concrete Methods)
        • 在抽象类中实现一些不变化的方法,即那些构成算法骨架的共同步骤。
        • 通常是私有(Private)或者最终(Final)的,以确保子类不会改变这些方法。
      • 抽象操作(Abstract Operations)
        • 在抽象类中声明的一些抽象方法,它们代表了算法中需要由子类实现的可变部分。
        • 通常这些抽象操作对应于算法的某些特定步骤,子类将根据具体需求来实现这些步骤。
  • 具体子类(Concrete Class)
    • 继承自抽象类的具体子类。
    • 实现或覆盖抽象操作以提供具体的实现细节。
    • 如果有需要,也可以覆盖钩子方法来影响算法的某些部分。
  • 客户端(Client)
    • 调用抽象类中的模板方法,通常会通过一个具体子类的实例来进行调用。

3. 代码示例

3.1 示例1–在线购物系统
  • 假设正在开发一个在线购物系统,其中有不同的支付方式(如信用卡支付、支付宝支付等)。每种支付方式可能有一些共通的步骤,如验证支付信息、处理支付等,但也有一些特定的步骤,如与支付网关交互。
abstract class Payment {

    // 模板方法,定义了支付处理的流程
    public final void processPayment() {
        validatePaymentInfo();
        performPrePaymentOperations();
        handlePayment();
        performPostPaymentOperations();
    }

    // 抽象方法,由具体子类实现
    protected abstract void validatePaymentInfo();

    protected abstract void handlePayment();

    // 钩子方法,可以有默认实现,子类可以选择覆盖
    protected void performPrePaymentOperations() {
        System.out.println("Performing pre-payment operations (default implementation).");
    }

    // 钩子方法,可以有默认实现,子类可以选择覆盖
    protected void performPostPaymentOperations() {
        System.out.println("Performing post-payment operations (default implementation).");
    }
}

// 信用卡支付子类
class CreditCardPayment extends Payment {

    @Override
    protected void validatePaymentInfo() {
        System.out.println("Validating credit card payment info.");
        // 实现具体的验证逻辑
    }

    @Override
    protected void handlePayment() {
        System.out.println("Handling credit card payment with payment gateway.");
        // 实现与支付网关交互的逻辑
    }

    // 覆盖钩子方法以提供自定义行为
    @Override
    protected void performPostPaymentOperations() {
        System.out.println("Updating order status after credit card payment.");
        // 实现更新订单状态的逻辑
    }
}

// 支付宝支付子类
class AlipayPayment extends Payment {

    @Override
    protected void validatePaymentInfo() {
        System.out.println("Validating Alipay payment info.");
        // 实现具体的验证逻辑
    }

    @Override
    protected void handlePayment() {
        System.out.println("Handling Alipay payment with Alipay gateway.");
        // 实现与支付宝网关交互的逻辑
    }

    // 不需要覆盖钩子方法,使用默认实现
}

public class PaymentProcessor {
    public static void main(String[] args) {
        Payment creditCardPayment = new CreditCardPayment();
        Payment alipayPayment = new AlipayPayment();

        // 处理信用卡支付
        creditCardPayment.processPayment();

        // 处理支付宝支付
        alipayPayment.processPayment();
    }
}
  • 将看到如下输出:
Validating credit card payment info.
Performing pre-payment operations (default implementation).
Handling credit card payment with payment gateway.
Updating order status after credit card payment.
Validating Alipay payment info.
Performing pre-payment operations (default implementation).
Handling Alipay payment with Alipay gateway.
Performing post-payment operations (default implementation).
  • 在这个例子中,Payment 类定义了支付处理的算法骨架,CreditCardPayment 和 AlipayPayment 子类则提供了具体实现的细节。processPayment 方法作为模板方法,确保每种支付方式都遵循相同的处理流程,而子类则负责实现各自特有的验证和支付逻辑。同时,钩子方法 performPrePaymentOperations 和 performPostPaymentOperations 提供了额外的扩展点,允许子类在需要时插入自定义的行为。

3.2 示例2–文件处理系统
  • 定义一个处理文件的通用流程,但具体的文件读取和写入操作可以由不同的子类来实现。
// 抽象类,定义文件处理的模板方法  
abstract class FileProcessor {

    // 模板方法,定义了文件处理的流程  
    public final void processFile(String filePath) {
        System.out.println("Starting file processing...");

        // 读取文件内容  
        String content = readFile(filePath);

        // 对读取的内容进行处理  
        String processedContent = processContent(content);

        // 将处理后的内容写入文件  
        writeFile(processedContent, filePath);

        System.out.println("File processing completed.");
    }

    // 抽象方法,由子类提供具体的实现  
    protected abstract String readFile(String filePath);

    // 抽象方法,由子类提供具体的实现  
    protected abstract String processContent(String content);

    // 抽象方法,由子类提供具体的实现  
    protected abstract void writeFile(String content, String filePath);

    // 可以提供一个钩子方法,允许子类在必要时覆盖  
    protected void beforeProcessing() {
        System.out.println("Before processing hook method.");
    }

    // 可以提供一个钩子方法,允许子类在必要时覆盖  
    protected void afterProcessing() {
        System.out.println("After processing hook method.");
    }
}

// 具体类1,实现文件处理器,用于处理文本文件  
class TextFileProcessor extends FileProcessor {

    @Override
    protected String readFile(String filePath) {
        // 实现读取文本文件的逻辑  
        System.out.println("Reading text file: " + filePath);
        // 假设这里返回读取到的文件内容  
        return "Content from text file";
    }

    @Override
    protected String processContent(String content) {
        // 实现处理文本文件内容的逻辑  
        System.out.println("Processing text content...");
        // 假设这里对内容进行了处理  
        return "Processed text content";
    }

    @Override
    protected void writeFile(String content, String filePath) {
        // 实现写入文本文件的逻辑  
        System.out.println("Writing to text file: " + filePath);
        // 假设这里将内容写入了文件  
    }

    // 可以选择覆盖钩子方法  
    @Override
    protected void beforeProcessing() {
        System.out.println("Before processing text file.");
    }
}

// 具体类2,实现文件处理器,用于处理二进制文件  
class BinaryFileProcessor extends FileProcessor {

    @Override
    protected String readFile(String filePath) {
        // 实现读取二进制文件的逻辑  
        System.out.println("Reading binary file: " + filePath);
        // 假设这里返回读取到的二进制内容(转换为字符串表示)  
        return "Content from binary file";
    }

    @Override
    protected String processContent(String content) {
        // 实现处理二进制文件内容的逻辑  
        System.out.println("Processing binary content...");
        // 假设这里对内容进行了处理  
        return "Processed binary content";
    }

    @Override
    protected void writeFile(String content, String filePath) {
        // 实现写入二进制文件的逻辑  
        System.out.println("Writing to binary file: " + filePath);
        // 假设这里将内容写入了文件  
    }
}

// 主类,用于演示模板方法模式的使用  
public class FileProcessorDemo {
    public static void main(String[] args) {
        // 创建文本文件处理器对象  
        FileProcessor textFileProcessor = new TextFileProcessor();
        // 使用文本文件处理器处理文件  
        textFileProcessor.processFile("path/to/text/file.txt");

        // 创建二进制文件处理器对象  
        FileProcessor binaryFileProcessor = new BinaryFileProcessor();
        // 使用二进制文件处理器处理文件  
        binaryFileProcessor.processFile("path/to/binary/file.bin");
    }
}

  • 将看到如下输出:
Starting file processing...
Reading text file: path/to/text/file.txt
Processing text content...
Writing to text file: path/to/text/file.txt
File processing completed.
Starting file processing...
Reading binary file: path/to/binary/file.bin
Processing binary content...
Writing to binary file: path/to/binary/file.bin
File processing completed.
  • 在这个例子中,FileProcessor 是一个抽象类,它定义了一个 processFile 模板方法,该方法按照预定的顺序调用了三个抽象方法:readFile、processContent 和 writeFile。这些抽象方法必须由子类提供具体的实现。
  • TextFileProcessor 和 BinaryFileProcessor 是 FileProcessor 的具体子类,分别用于处理文本文件和二进制文件。每个子类都提供了 readFile、processContent 和 writeFile 方法的具体实现,以符合它们各自处理文件类型的特定需求。
  • FileProcessorDemo 类是主类,用于演示如何使用这两个具体的文件处理器。通过创建 TextFileProcessor 和 BinaryFileProcessor 的实例,并调用它们的 processFile 方法,可以执行文件处理流程。

4. 优缺点

  • 主要作用
    • 定义一个操作中的算法骨架,并将一些步骤延迟到子类中实现。
  • 优点
    • 代码复用:通过定义稳定的操作序列,减少了重复代码的编写,提高了代码复用性。
    • 结构清晰:将不变的部分和可变的部分分离,使得操作流程更加清晰和易于理解。
    • 扩展性好:子类可以通过实现抽象方法来定制特定步骤,从而在不修改原有结构的情况下进行扩展。
    • 易于维护:由于操作流程被统一管理,当需要修改流程时,只需在抽象类或模板方法中修改,减少了维护工作量。
  • 缺点
    • 抽象类的数量增加:为了使用模板方法,可能需要创建新的抽象类。
    • 代码的复杂性:对于初学者而言,理解模板方法的继承和多态特性可能较为复杂。
    • 子类的依赖性增加:子类需要依赖父类的具体实现,这可能导致子类与父类之间的耦合度增加。

5. 应用场景

5.1 主要包括以下几个方面
  1. 有固定处理步骤的算法:当一个复杂的算法或流程中的某些步骤在各个子类中实现相同,而某些步骤需要根据具体子类实现不同功能时,可以使用模板方法模式。
  2. 代码复用和扩展性要求高的场景:如果希望在不改变算法结构的前提下,通过继承机制来扩展程序的功能,模板方法模式可以实现这一点。
  3. 有多个子类共享相似处理过程的情况:当多个子类具有类似的处理流程,但部分处理步骤在各子类间有所差异时,模板方法可以将这些步骤抽象化,并在抽象类中定义共同的处理过程。
  4. 插件式系统:当希望系统能够动态地加载和卸载功能模块时,模板方法模式允许将新功能的实现作为插件插入到系统中,而不需要修改现有代码。
  5. 框架开发:在设计软件框架时,模板方法模式可以用来定义框架的基础结构和默认行为,同时允许用户通过继承机制定制特定行为。

5.2 实际应用
  1. 文件读取和处理:当需要从不同类型的文件中读取数据并进行处理时,可以使用模板方法模式。首先定义一个抽象类,其中包含读取文件、解析数据和处理数据的步骤。然后,为每种文件类型创建一个子类,实现具体的读取和解析方法。这样,可以确保处理流程的一致性,同时根据不同的文件类型提供特定的实现。
  2. 图形绘制:在图形绘制系统中,可以使用模板方法模式来定义绘制不同形状的算法骨架。首先定义一个抽象类,其中包含绘制形状的步骤,如设置画笔颜色、绘制边框等。然后,为每种形状创建一个子类,实现具体的绘制方法。这样,可以确保绘制流程的一致性,同时根据不同的形状提供特定的实现。
  3. 网络请求处理:在网络应用程序中,可以使用模板方法模式来处理不同类型的网络请求。首先定义一个抽象类,其中包含发送请求、接收响应和处理结果的步骤。然后,为每种请求类型创建一个子类,实现具体的发送和接收方法。这样,可以确保请求处理流程的一致性,同时根据不同的请求类型提供特定的实现。
  4. 游戏开发:在游戏开发中,可以使用模板方法模式来定义游戏的主循环。首先定义一个抽象类,其中包含更新游戏状态、渲染画面和处理用户输入的步骤。然后,为每种游戏类型创建一个子类,实现具体的更新和渲染方法。这样,可以确保游戏循环的一致性,同时根据不同的游戏类型提供特定的实现。

6. JDK中的使用

  • 集合框架:在java.util.Collections类中,例如sort()方法就是一个模板方法。它定义了排序的算法骨架,具体的比较逻辑则由实现Comparator接口的子类来完成。
  • I/O流:java.io.InputStream和java.io.OutputStream等抽象类中使用了模板方法模式。它们定义了读取和写入数据的基本步骤,而具体如何读取和写入则由子类如FileInputStream和FileOutputStream来实现。
  • JUnit测试框架:在JUnit中,TestCase类使用了模板方法模式。setUp()和tearDown()方法定义了测试用例执行前后的准备工作和清理工作,而具体的测试逻辑则在继承自TestCase的子类中实现。

7. 注意事项

  • 合理使用:模板方法模式适用于有固定算法结构,但某些步骤需要在不同子类中具体实现的情况。不是所有的场景都需要使用模板方法模式,只有在多个子类中需要重复实现一些通用方法或算法时,才考虑使用它。
  • 区分钩子方法:模板方法模式中的钩子方法是一种可选的扩展点,它们可以为子类提供覆盖超类中某些步骤的机会。正确理解和使用钩子方法与模板方法的关系,可以帮助更好地实现算法的可定制性。
  • 避免滥用:尽管模板方法模式可以提高系统的扩展性,但过度使用可能导致代码复杂性和维护难度增加。因此,应当在确实需要时才使用模板方法模式。
  • 关注子类实现:在使用模板方法模式时,需要确保子类正确地实现了所有抽象方法,否则可能导致运行时错误或逻辑不一致。

8. 模板方法模式 VS 建造者模式

模式类型目的模式架构主要角色应用场景
模板方法模式行为型定义一个操作中的算法骨架,并将一些步骤延迟到子类中实现。抽象类和具体方法类适用于有固定处理步骤的算法,但某些步骤需要在不同的子类中实现时使用。
建造者模式创建型将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。Builder、ConcreteBuilder、Director 和 Product适用于创建复杂对象

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

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

相关文章

Java+saas模式 智慧校园系统源码MySQL5.7+ elmentui前后端分离架构 让校园管理更高效的数字化平台系统源码

Javasaas模式 智慧校园系统源码MySQL5.7 elmentui前后端分离架构 让校园管理更高效的数字化平台系统源码 智慧校园是在数字通增强版基础上,研发的一套面向教育行业的数字化校园软件,其显著特点是集学校网站、协同办公、即时通讯、网络空间、移动办公于一…

接口自动化测试之调用excel实现接口数据依赖

背景 我们把接口的信息按照规定的格式都维护在excel文件中,通过代码实现调用excel,完成接口的自动化测试。这样,只需要负责人将主要逻辑写好之后,公司其他不会写代码的员工,也可以通过维护excel中的接口数据&#xff…

C语言 递归

递归指的是在函数的定义中使用函数自身的方法。 举个例子: 从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚&…

【算法练习】29:插入排序学习笔记

一、插入排序的算法思想 原理:将一个无序的数据序列逐步转化为有序序列。算法将待排序的数组分为两个部分已排序部分和未排序部分。 时间复杂度:插入排序的时间复杂度在最坏、平均和最好情况下的表现相同,均为 ,其中 n 是待排序数…

OpenHarmony轻量系统开发【10】编写自己的软件包

10.1 添加第一个a_myparty软件包 打开鸿蒙系统的源码,可以看到有这么一个文件夹:third_party。里面存放的是第三方的代码。 点开我们可以看到有很多第三方代码: 后续我们如果需要往系统中添加、移植任何开源代码,都可以添加到这个…

ChatGPT们写高考作文会发生什么?

2023年的高考结束了,今年共有1291万考生参加了高考了,再次创造了历史参考人数之最。在高考中,要说什么最引人讨论,那高考作文当仁不让啊。今年随着ChatGPT的爆火,可谓是给文字工作者带来一大助力,如果让AI来…

做测试,不会接口测试怎么能行?

接口测试的测试点 功能测试:单接口功能、业务场景功能 性能测试:响应时长、错误率、吞吐量、服务器资源使用率 安全测试:敏感数据是否加密、SQL注入、其他 本篇文章以接口功能测试为主。 接口用例设计方法: 单接口测试用例设…

在网上打印资料多少钱一张

随着互联网的普及和线上服务的完善,越来越多的人选择在网上打印资料。这种方式不仅方便快捷,而且通常价格更为透明和实惠。那么,在网上打印资料到底多少钱一张呢?这主要取决于您选择的打印平台、纸张规格、打印质量以及打印数量等…

SpringBoot的旅游管理系统+论文+ppt+免费远程调试

项目介绍: 基于SpringBoot旅游网站 旅游管理系统 本旅游管理系统采用的数据库是Mysql,使用SpringBoot框架开发。在设计过程中,充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。 (1&…

ModuleNotFoundError: No module named ‘llama_index.readers“解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Day55 动态规划 part15

Day55 动态规划 part15 392.判断子序列 我的思路: 自己还是只能想到双指针法 解答: class Solution {public boolean isSubsequence(String s, String t) {if(s.length() 0) {return true;}if(s.length() > t.length() || t.length() 0) {return false;}ch…

牛客小白月赛90(A,B,C,D,E,F)

比赛链接 官方题解(视频) 这场偏思维,感觉好像没啥算法。E需要动态维护前 k k k 小,F是个离散化加dp,状态和递推方程比较 非常规,建议还是看一下涨涨姿势。 A 小A的文化节 思路: 签到 cod…

都2024年了,还不知道怎么学习网络安全?来看看吧,很难找全的

前言 最近收到不少关注朋友的私信和留言,大多数都是零基础小友入门网络安全,需要相关资源学习。其实看过的铁粉都知道,之前的文里是有过推荐过的。新来的小友可能不太清楚,这里就系统地叙述一遍。 01.简单了解一下网络安全 说白…

CUDA编程---全局内存

CUDA内存模型概述 内存的访问和管理是所有编程语言的重要部分。在现代加速器中,内存管理对高性能计算有着很大的影响。因为多数工作负载被加载和存储数据的速度所限制,所以有大量低延迟、高带宽的内存对性能是十分有利的。 然而,大容量、高性…

如何做信创测试

信创测试是一种系统化的方法,旨在评估和验证创意和创新项目的潜力和可行性。进行信创测试可以帮助企业在投入大量资源之前,对创意进行客观、科学的评估,以减少失败的风险并最大化成功的可能性。以下是一般性的信创测试步骤: 确定…

Linux的学习之路:11、地址空间

摘要 本章主要是说一下地址空间,我也只是按照我的理解进行解释,可能说不清楚,欢迎指正 目录 摘要 一、空间布局图 二、代码测试一下 三、进程地址空间 四、测试代码 一、空间布局图 如下方图片可以看出地址空间有几种,这里…

成都欣丰洪泰文化传媒有限公司领航电商新纪元

在当今数字化飞速发展的时代,电商行业异军突起,成为推动经济增长的重要力量。在这股浪潮中,成都欣丰洪泰文化传媒有限公司以其专业的电商服务脱颖而出,成为业界的佼佼者。本文将带您一探这家公司的独特魅力和专业服务,…

MySQL查看表大小

1、使用SHOW TABLE STATUS命令: SHOW TABLE STATUS LIKE table_name;上述命令会返回包含表格信息的一行结果,其中有一个列为Data_length,表示表格占用的数据空间大小(以字节为单位)。 2、使用INFORMATION_SCHEMA库的…

Docker 学习笔记(五):梳理 Docker 镜像知识,附带 Commit 方式提交镜像副本,安装可视化面板 portainer

一、前言 记录时间 [2024-4-10] 前置文章: Docker学习笔记(一):入门篇,Docker概述、基本组成等,对Docker有一个初步的认识 Docker学习笔记(二):在Linux中部署Docker&…

吴恩达2022机器学习专项课程(一) 第二周课程实验:使用 scikit-learn 进行线性回归(Lab_05 Lab_06)

目标 使用scikit-learn实现线性回归(SGDRegressor和LinearRegression)。 1.什么是scikit-learn? 一个用于 Python 编程语言的开源机器学习库,用于实现各种机器学习算法。 2.特征缩放(Z标准化) 第一步先使用Z标准化处理训练样本,减少训练…