探索设计模式的魅力:揭秘模版方法模式-让你的代码既灵活又可维护

在这里插入图片描述


设计模式专栏:http://t.csdnimg.cn/U54zu


目录

  • 一、开篇
  • 二、应用场景
    • 一坨坨代码实现
    • 存在的问题
  • 三、解决方案
    • 模式方法结构示意图及说明
    • 用模板方法模式重构示例
    • 解决的问题
  • 四、工作原理
    • 使用模板方法模式重写示例结构图
    • 核心结构:抽象类和具体实现
  • 五、总结
    • 优点
    • 缺点
    • 最佳实践
    • 与其他设计模式相结合

一、开篇

在软件开发中,设计一个灵活且易于维护的系统至关重要。模板方法模式作为面向对象设计模式之一,通过分离稳定与变化的部分,提高了代码复用性,确保了软件的可扩展性。它是构建高效软件蓝图的关键,为设计可扩展架构提供了高效方法。

    在开发复杂的软件时,我们经常会遇到两个主要的问题:

1. 如何减少代码冗余,提高复用性?
2. 如何确保软件容易维护和扩展?

    为了解决这些问题,我们需要采用一种设计模式,它能够将不变的部分与变化的部分清晰地分离开来,从而允许程序员在不改变稳定算法结构的前提下,自由地扩展和修改那些容易变化的部分。

模板方法模式是一种基于继承的设计模式,它定义了一个操作中的算法骨架,并将一些步骤的实现延迟到子类。通过这种方式,模板方法允许子类在不改变算法结构的情况下,重新定义算法的某些特定步骤。

二、应用场景

你正在编写一个应用程序,该程序可以处理多种类型的文档转换。尽管转换过程中的一些步骤对所有文档类型都是通用的,比如打开文档、读取数据、和储存转换后的文档,但是具体的转换逻辑对每种文档类型来说都是不同的。

一坨坨代码实现

    不使用设计模式的情况下,你可以简单地对每种转换类型创建一个方法,并在这些方法中复制粘贴相同的步骤代码。以下是一个未使用模板方法模式的示例代码实现:

public class DocumentConverter {public void convertPDFToWord(String inputFile, String outputFile) {// 打开 PDF 文档System.out.println("Opening PDF document: " + inputFile);// 读取数据System.out.println("Reading data from PDF document.");// PDF to Word 转换的特定逻辑System.out.println("Converting PDF to Word.");// 储存转换后的 Word 文档System.out.println("Saving Word document: " + outputFile);}public void convertXMLToCSV(String inputFile, String outputFile) {// 打开 XML 文档System.out.println("Opening XML document: " + inputFile);// 读取数据System.out.println("Reading data from XML document.");// XML to CSV 转换的特定逻辑System.out.println("Converting XML to CSV.");// 储存转换后的 CSV 文档System.out.println("Saving CSV document: " + outputFile);}// 可能还有更多针对不同文档类型的转换方法...
}public class DocumentConversionExample {public static void main(String[] args) {DocumentConverter converter = new DocumentConverter();// 转换PDF到Wordconverter.convertPDFToWord("example.pdf", "example.docx");// 转换XML到CSVconverter.convertXMLToCSV("example.xml", "example.csv");// ...执行更多转换}
}

    在这个例子中,convertPDFToWord 和 convertXMLToCSV 方法各自包含打开文档、读取数据、执行转换及存储文档的步骤。这意味着代码中存在大量重复,如果转换流程的某些通用步骤发生变更,你需要在每一个转换方法中单独进行修改,这显然违背了 DRY(Don’t Repeat Yourself)原则。

存在的问题

 1. 代码重复:convertPDFToWord 和 convertXMLToCSV 方法都包含了很多相同的代码,例如打开文档和读取数据。每种转换方法都重复了这些通用的步骤,这不仅增加了代码量也增加了维护成本。

 2. 难以维护:因为相同的代码分散在不同的方法中,所以如果通用步骤需要修改,你必须找到所有的副本并逐一进行修改,这非常容易导致错误。

 3. 扩展性差:如果需要新增一种文档类型的转换,你要再次复制粘贴同样的代码,并对新类型的文档实现转换逻辑。这不仅仅是低效的,更是容易出错的。

 4. 高耦合性:每种转换方法不仅包含了特定的转换逻辑,还混入了通用的处理步骤。这导致了转换逻辑与通用处理逻辑高度耦合,降低了代码的可读性和可测试性。

 5. 难以测试:因为每个转换方法都包含多步操作,这使得为特定步骤编写单元测试变得更加困难。你可能需要获取或模拟中间数据以便测试方法中的某个步骤。

 6. 违反了软件设计的原则:如上一点所述,这样的代码结构违反了DRY原则(不要重复自己),同时也违背了单一职责原则,因为每个转换方法除了处理特定的转换逻辑之外,还负责了文件的打开和保存等操作

    使用模板方法模式可以解决上述问题,它允许将通用逻辑抽取到一个基类中,并通过定义抽象方法来让子类实现特定实现细节。这样,通用代码只需要写一次,在基类中,减少了重复,同时提供了更好的维护性和可扩展性。


三、解决方案

 定义

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

模式方法结构示意图及说明

在这里插入图片描述

  • AbstractClass:抽象类。用来定义算法骨架和原语操作,具体的子类通过重定义这些原语操作来实现一个算法的各个步骤。在这个类里面,还可以提供算法中通用的实现。
  • ConcreteClass:具体实现类。用来实现算法骨架中的某些步骤,完成与特定子类相关的功能。

用模板方法模式重构示例

重构步骤

 1. 创建一个抽象基类,定义转换文档的通用步骤和模板方法。
 2. 在基类中实现通用步骤(打开文件、读取数据、保存文件)。
 3. 定义一个或多个抽象方法,子类必须实现这些方法以执行特定的转换逻辑。
 4. 为每种类型的文档创建具体子类,实现特定的转换逻辑。

实现代码

public abstract class AbstractDocumentConverter {// 模板方法,定义算法骨架public final void convertDocument(String inputFile, String outputFile) {openDocument(inputFile);readData();convert();saveDocument(outputFile);}protected void openDocument(String inputFile) {// 实现通用的文件打开逻辑System.out.println("Opening document: " + inputFile);}protected void readData() {// 实现通用的数据读取逻辑System.out.println("Reading data from document.");}protected abstract void convert(); // 抽象方法,子类将实现具体的转换逻辑protected void saveDocument(String outputFile) {// 实现通用的文件保存逻辑System.out.println("Saving document: " + outputFile);}
}public class PDFToWordConverter extends AbstractDocumentConverter {@Overrideprotected void convert() {// 实现 PDF 到 Word 的特定转换逻辑System.out.println("Converting PDF to Word.");}
}public class XMLToCSVConverter extends AbstractDocumentConverter {@Overrideprotected void convert() {// 实现 XML 到 CSV 的特定转换逻辑System.out.println("Converting XML to CSV.");}
}// ...可以为其他类型的文档添加更多的子类public class DocumentConversionExample {public static void main(String[] args) {AbstractDocumentConverter pdfToWordConverter = new PDFToWordConverter();pdfToWordConverter.convertDocument("example.pdf", "example.docx");AbstractDocumentConverter xmlToCsvConverter = new XMLToCSVConverter();xmlToCsvConverter.convertDocument("example.xml", "example.csv");// ...使用其他转换器执行更多转换}
}

  采用模板方法模式后,我们有以下改进:

  • 基类 AbstractDocumentConverter 定义了通用步骤的实现,并提供了一个模板方法 convertDocument,它定义了转换文档的步骤序列。
  • 各个步骤中,唯一必须由各个子类实现的步骤是 convert,它包含了特定于不同文档类型转换的逻辑。
  • PDFToWordConverterXMLToCSVConverter 是具体的转换器类,继承自 AbstractDocumentConverter,并实现了 convert 抽象方法,用于实现特定的转换逻辑。

解决的问题

 1. 减少了代码重复:通用逻辑(如打开文件、读取数据、保存文件)现在在基类中实现一次即可,避免在每个转换器中重复相同的代码。

 2. 更易于维护:所有通用步骤都集中在基类中,如果需要更改这些步骤的实现,只需在一个地方修改即可,而不是在每个具体实现中逐一更改。

 3. 提高了扩展性:新的文档转换器可以通过创建新的子类很容易地添加,只需实现特定的转换逻辑而无需关心通用流程。

 4. 降低耦合性:模板方法模式将通用过程和具体步骤分开,将变化部分封装在子类中,基类和子类之间的耦合度降低,使代码更加模块化。

 5. 易于测试:可以独立测试文档转换的通用步骤和特定步骤。例如,可以单独对子类的convert方法进行单元测试,而不必担心文件打开、数据读取等通用逻辑。

 6. 遵守设计原则:通过使用模板方法模式,代码遵守了DRY原则(Don’t Repeat Yourself,不重复自己),因为它消除了代码重复。同时也遵循了单一职责原则,因为基类只关心定义算法骨架和执行通用步骤,而具体的转换逻辑则由各个子类负责。

 7. 更清晰的职责划分:模板方法定义了算法的骨架,使得算法的结构更加明晰,同时职责分配也更加清楚:基类负责算法的整体流程和通用步骤,子类负责具体的细节和个性化实现。

  通过使用模板方法模式,重构的代码变得更加健壮、易于扩展与维护,同时更贴近面向对象设计的最佳实践。

四、工作原理

使用模板方法模式重写示例结构图

在这里插入图片描述

    模板方法模式属于行为型设计模式的一种,它定义了一个操作中的算法的骨架,将某些步骤延迟到子类中实现。这使得子类可以在不改变算法结构的情况下重新定义算法中某些特定步骤的实现。

核心结构:抽象类和具体实现

 抽象类:这是模板方法模式的关键所在。抽象类包含了算法的模板,即一系列定义好的操作(方法)序列,我们称之为“模板方法”。这个方法将各个需要在子类实现的抽象操作定义为算法的一个部分。此外,它还可以包含一些实现,为子类提供辅助函数和通用功能。

 具体实现:在具体实现中,子类将重写抽象类中的抽象方法,提供特定步骤的实现。子类可以有不同的行为,但它们通过父类提供的模板方法来调用这些方法,从而保持一致的算法结构。

五、总结

模板方法模式是一个非常有用的设计模式,特别是在算法步骤分明、稳定,并且可以由子类在没有影响整体执行顺序的情况下提供或修改部分实现的情况下。然而,开发人员应该评估这种模式是否能够满足实际项目需求,抑或是它可能会带来的设计复杂性和灵活性限制。正确的做法是,在确信模板方法模式可以为项目带来显著优势的时候,再将其作为解决特定问题的工具。

优点

  • 代码复用:
    模板方法通过在抽象类中实现通用代码,减少了子类中的重复代码,有助于避免错误并提高代码维护性。
  • 扩展性:
    新增特定行为的子类很容易,不需要修改已有的抽象类代码,只需扩展并实现必要的抽象操作即可。
  • 封装不变部分:
    模板方法模式封装了算法框架和不变部分,使得变化可局部化,这有助于代码的稳定性和可预测性。
  • 控制反转:
    子类重写的方法由父类的模板方法在适当的时间调用,这在软件工程中称为“控制反转”,有助于解耦算法的实现和客户端的使用。
  • 符合开闭原则:
    算法的核心流程一旦定义完成就不再改变,新的步骤实现和变化都在子类中完成,符合面向对象设计的开闭原则(对扩展开放,对修改封闭)。

缺点

  • 限制灵活性:
    由于算法框架是固定的,如果需要改动算法的某个特定步骤可能会很困难,尤其是当步骤的执行顺序在算法中非常重要时。
  • 高层次耦合:
    子类的实现必须依赖于抽象类中定义的模板结构,这可能导致较强的耦合性,同时也可能会违反里氏替换原则(子类能够替换掉它们的基类)。
  • 难以理解:
    抽象和子类之间的关系对于新的开发人员来说可能不那么直观,需要花费额外时间理解模板方法的整个流程和执行顺序。
  • 过多使用导致继承膨胀:
    如果过度依赖模板方法模式,可能会造成一个庞大的继承体系,增加了理解和维护这些类的难度。
  • 设计限制:
    模板方法模式通常需要在设计之初就有预见性地将变与不变分离,一旦系统需要变动不在预期内的部分,模板方法可能就显得不够灵活。

最佳实践

 1. 当你有一系列步骤形成算法,并且算法的部分步骤在不同上下文中会有不同实现时,使用模板方法模式。
 2. 尽量减少抽象类中的具体方法,避免抽象类变得臃肿。
 3. 仅在算法的步骤确实固定,且变化不大时选用模板方法模式。
 4. 尝试用钩子方法给子类更多的扩展点,以增加灵活性。
 5. 确保使用模板方法模式不会使得你的类层次变得过于复杂。

与其他设计模式相结合

    模板方法模式可以与其他设计模式相结合,以进一步提升软件设计的优雅性和效率。

  • 工厂方法模式与模板方法模式结合,可以在模板方法的某些步骤中使用工厂方法来创建对象,进一步解耦了对象的创建与算法的实现。
  • 策略模式可以用来增加算法步骤的灵活性,特别是当你想在运行时动态改变算法的某些行为而不仅仅是在编译时。
  • 状态模式很自然地与模板方法模式相结合,尤其是当对象的行为需要根据它的状态变化而变化时,可以将这些行为作为模板方法中的步骤。
  • 装饰者模式可以在不更改现有对象的代码的情况下,为对象添加新的行为,这在模板方法的步骤中非常有用。

    通过将模板方法模式与其他设计模式相结合,我们可以赋予软件架构更大的灵活性和可扩展性,这将有助于应对软件发展过程中的变化,同时保持代码的简洁和清晰。如同艺术家在画布上混合色彩以达到完美的色调和层次感,软件设计师也可以灵活运用不同的设计模式,去构筑更为强大和优雅的软件架构。

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

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

相关文章

【Effective Objective - C】—— 内存管理

【Effective Objective - C】—— 内存管理 前言29.理解引用计数引用计数工作原理关闭ARC模式属性存取方法中的内存管理自动释放池保留环要点 30.以ARC简化引用计数使用ARC时必须遵守的方法命名规则变量的内存管理语义ARC如何清理实例变量要点 31.在dealloc方法中只释放引用并解…

基于深度置信网络的多模态过程故障评估方法及应用

源自:自动化学报 作者:张凯, 杨朋澄, 彭开香, 陈志文 “人工智能技术与咨询” 发布 摘 要 传统的多模态过程故障等级评估方法对模态之间的共性特征考虑较少, 导致当被评估模态故障信息不充分时, 评估的准确性较低. 针对此问题, 首先, 提出一种共性–…

Arrays工具类的常见方法总结

一、Arrays.asList( ) 1.作用&#xff1a;Arrays.asList( )方法的作用是将数组转换成List&#xff0c;将List中的全部集合对象添加至ArrayList集合中 2.参数&#xff1a;动态参数 (T... a) 3.返回值&#xff1a;List 集合 List<T> 4.举例&#xff1a; package com…

删除windows自带输入法

ctrl shift F 搜狗简繁体切换

【第二十四课】二分图:acwing-860染色法判定二分图 / acwing-861二分图的最大匹配 ( c++代码 )

目录 二分图是什么 acwing-860染色法判定二分图 染色法 代码 acwing-861二分图的最大匹配 思路 代码 二分图是什么 学习二分图的目的就是一些题目可以简化成二分图的模型来求解。 二分图也就是&#xff1a;一个无向图顶点集&#xff0c;分成了两堆顶点(可以理解为两…

分布式文件系统 SpringBoot+FastDFS+Vue.js【三】

分布式文件系统 SpringBootFastDFSVue.js【三】 七、创建后台--分角色管理7.1.创建后台数据库表7.2.创建实体类7.2.1.Admin7.2.2.Menu7.2.3.MenuBean7.2.4.Role7.2.5.RoleMenu 7.3.编辑配置文件application.yml7.4.编写工具类7.4.1.AuthContextHolder7.4.2.HttpUtils7.4.3.Stri…

《Go 简易速速上手小册》第7章:包管理与模块(2024 最新版)

文章目录 7.1 使用 Go Modules 管理依赖 - 掌舵向未来7.1.1 基础知识讲解7.1.2 重点案例&#xff1a;Web 服务功能描述实现步骤扩展功能 7.1.3 拓展案例 1&#xff1a;使用数据库功能描述实现步骤扩展功能 7.1.4 拓展案例 2&#xff1a;集成 Redis 缓存功能描述实现步骤扩展功能…

Sora 和之前 Runway 那些在架构上有啥区别呢?

问&#xff1a;Sora 和之前 Runway 那些在架构上有啥区别呢&#xff1f; 答&#xff1a;简单来说 Runway 是基于扩散模型&#xff08;Diffusion Model&#xff09;的&#xff0c;而 Sora 是基于 Diffusion Transformer。 Runway、Stable Diffusion 是基于扩散模型&#xff08…

MySQL 插入10万条数据性能分析

MySQL 插入10万条数据性能分析 一、背景 笔者想复现一个索引失效的场景&#xff0c;故需要一定规模的数据作支撑&#xff0c;所以需要向数据库中插入大约一百万条数据。那问题就来了&#xff0c;我们应该怎样插入才能使插入的速度最快呢&#xff1f; 为了更加贴合实际&#…

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

如何用AI绘画工具最好最省时省事的方法制作个性化头像框?

原文章链接&#xff1a;如何根据游戏素材制作主题头像框&#xff1f;实战教程来了&#xff01; - 优设网 - 学设计上优设 教程专区&#xff1a;AI绘画&#xff0c;AI视频&#xff0c;AI写作等软件类型AI教程&#xff0c; AI工具专区&#xff1a;AI工具-喜好儿aigc 在 APP 的…

「算法」二分查找1:理论细节

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;算法详解 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 二分查找算法简介 这个算法的特点就是&#xff1a;细节多&#xff0c;出错率高&#xff0c;很容易就写成死循环有模板&#xff0c;但…

如何在UI自动化测试中加入REST API的操作

1、问题 当我们描述一个“好的自动化测试用例”时&#xff0c;经常出现标准是&#xff1a; 精确 自动化测试用例应该测试一件事&#xff0c;只有一件事。与测试用例无关的应用程序的某个部分中的错误不应导致测试用例失败。 独立 自动化测试用例不应该受测试套件中任何其他…

PyTorch-线性回归

已经进入大模微调的时代&#xff0c;但是学习pytorch&#xff0c;对后续学习rasa框架有一定帮助吧。 <!-- 给出一系列的点作为线性回归的数据&#xff0c;使用numpy来存储这些点。 --> x_train np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168],[9.779], [6.1…

win32汇编获取系统信息

.data fmt db "页尺寸&#xff1a;%d",0 db "" lpsystem SYSTEM_INFO <?> szbuf db 200 dup(0) .const szCaption db 系统信息,0 .code start: invoke GetSystemInfo,addr lpsystem …

Java编程在工资信息管理中的最佳实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

用Java实现简单的图书管理系统

目录 1.总体框架 2.book包 Books类 booklist类 3.operation包 IO接口&#xff1a; addbooks类&#xff1a; borrowbooks类&#xff1a; delbooks类&#xff1a; returnbooks类&#xff1a; exit类&#xff1a; 4.user包 user类 Adminuser类&#xff08;难点&#…

嵌入式linux驱动开发篇之设备树

什么是设备树&#xff1f; 设备树&#xff08;Device Tree&#xff09;是一种用于描述嵌入式系统硬件组件及其连接关系的数据结构。它被广泛用于嵌入式 Linux 系统&#xff0c;尤其是针对使用多种不同架构和平台的嵌入式系统。它是一种与硬件描述相关的中间表示形式&#xff0c…

如何生成狗血短剧

如何生成狗血短剧 狗血短剧剧本将上述剧本转成对话 狗血短剧剧本 标题&#xff1a;《爱的轮回》 类型&#xff1a;现代都市爱情短剧 角色&#xff1a; 1. 林晓雪 - 女&#xff0c;25岁&#xff0c;职场小白&#xff0c;善良单纯 2. 陆子轩 - 男&#xff0c;28岁&#xff0c;公…

WINCC如何新增下单菜单,切换显示页面

杭州工控赖工 首先我们先看一下&#xff0c;显示的效果&#xff0c;通过下拉菜单&#xff0c;切换主显示页面。如图一&#xff1a; 图1 显示效果 第一步&#xff1a; 通过元件新增一个组合框&#xff0c;见图2&#xff1b; 组合框的设置&#xff0c;设置下拉框的长宽及组合数…