【再谈设计模式】模板方法模式 - 算法骨架的构建者

一、引言

        在软件工程、软件开发过程中,我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤,但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式(Template Method Design Pattern)就为解决这类问题提供了一个优雅的方案。它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中去实现,使得子类可以在不改变算法结构的情况下重新定义某些特定的步骤。

二、定义与描述

        模板方法设计模式是一种行为型设计模式。它包含一个抽象类(在Java和C++中)或者一个抽象基类(在Python中可以通过ABC抽象基类实现类似功能,在Go中通过接口和结构体组合来体现),这个抽象类中定义了一个模板方法,这个模板方法包含了算法的骨架,它按照一定的顺序调用其他的抽象方法或具体方法。抽象方法由子类去实现,从而实现不同的行为。

三、抽象背景

        假设我们正在开发一个游戏角色创建系统。游戏中有不同类型的角色,如战士、法师、刺客。每个角色在创建时有一些通用的步骤,例如选择种族、选择性别等,但也有一些特定于角色类型的步骤,比如战士要选择武器类型,法师要选择魔法元素,刺客要选择隐匿技能类型。这时候就可以使用模板方法设计模式来构建这个创建系统。

四、适用场景与现实问题解决

  • 场景一:框架开发
    • 在框架开发中,框架通常提供了一个固定的处理流程,但允许用户自定义某些特定的操作。例如,Web框架可能定义了处理HTTP请求的基本流程:接收请求、解析请求、处理业务逻辑、构建响应、发送响应。其中处理业务逻辑的部分可以由用户根据自己的需求定制。
    • 使用模板方法设计模式,框架可以将整个请求处理流程定义在一个抽象类中的模板方法里,而将处理业务逻辑的部分抽象成抽象方法,让用户通过继承抽象类并实现抽象方法来定制自己的业务逻辑。
  • 场景二:算法流程固定但部分可变
    • 例如排序算法中的希尔排序。希尔排序的基本思想是将数组按照一定的间隔进行分组,然后对每组进行插入排序,逐渐缩小间隔直到间隔为1。其中分组的计算和整体的排序流程是固定的,但每次分组后的插入排序步骤(比较和交换元素的操作)可以看作是一个可变的部分。
    • 可以使用模板方法设计模式,将希尔排序的整体流程定义在模板方法中,而将插入排序的操作抽象成抽象方法,这样如果要对插入排序进行优化或者修改,只需要在子类中重新实现这个抽象方法即可。

五、模板方法设计模式的现实生活的例子

  • 泡茶示例
    • 泡茶的基本步骤是固定的:准备茶具、烧开水、浸泡茶叶、倒入茶杯、添加调料(如糖、柠檬等,这一步可选)。这里烧开水、浸泡茶叶等步骤是固定的顺序,但不同的茶叶(如绿茶、红茶、黑茶)浸泡的时间和温度可能不同,添加调料的种类也可能不同。
    • 可以将泡茶的过程看作一个模板方法,其中准备茶具、烧开水等是固定的步骤,而浸泡茶叶和添加调料可以看作是抽象方法,根据不同的茶叶种类(子类)来具体实现。

六、初衷与问题解决

        初衷是为了在具有固定流程的算法或者业务逻辑中,提高代码的复用性和可维护性。通过将固定的流程放在模板方法中,将可变的部分抽象出来由子类实现,可以避免代码的重复编写,并且当业务逻辑发生变化时,只需要修改对应的子类即可,而不需要修改整个算法的结构。

七、代码示例

Java示例

abstract class GameCharacterCreator {
    // 模板方法,定义了创建角色的流程
    public final void createCharacter() {
        chooseRace();
        chooseGender();
        chooseClassSpecificOptions();
    }
    private void chooseRace() {
        System.out.println("选择种族");
    }
    private void chooseGender() {
        System.out.println("选择性别");
    }
    // 抽象方法,由子类实现
    abstract void chooseClassSpecificOptions();
}

class WarriorCreator extends GameCharacterCreator {
    @Override
    void chooseClassSpecificOptions() {
        System.out.println("选择武器类型");
    }
}

class MageCreator extends GameCharacterCreator {
    @Override
    void chooseClassSpecificOptions() {
        System.out.println("选择魔法元素");
    }
}

class AssassinCreator extends GameCharacterCreator {
    @Override
    void chooseClassSpecificOptions() {
        System.out.println("选择隐匿技能类型");
    }
}

类图:

        - GameCharacterCreator是一个抽象类,有一个公共的createCharacter方法(用+表示),一些私有方法(用-表示)和一个抽象方法(用#表示)。
        - WarriorCreatorMageCreatorAssassinCreator都是继承自GameCharacterCreator的具体类,并且实现了抽象方法chooseClassSpecificOptions

时序图: 

        以WarriorCreator为例,假设玩家创建战士角色。首先GameCharacterCreator引导玩家进行种族和性别的选择,然后WarriorCreator引导玩家进行战士特定的选项选择(这里是武器类型)。对于MageCreatorAssassinCreator可以类似表示,只是chooseClassSpecificOptions的内容不同。 

流程图:

        首先进行种族和性别的选择,然后根据选择的角色类型(战士、法师或刺客等)执行相应子类的特定选项选择方法,如果是未知角色类型则给出提示。

C++示例

class GameCharacterCreator {
public:
    // 模板方法,定义了创建角色的流程,final表示不能被子类重写
    void createCharacter() {
        chooseRace();
        chooseGender();
        chooseClassSpecificOptions();
    }
private:
    void chooseRace() {
        std::cout << "选择种族" << std::endl;
    }
    void chooseGender() {
        std::cout << "选择性别" << std::endl;
    }
    // 纯虚函数,相当于抽象方法,由子类实现
    virtual void chooseClassSpecificOptions() = 0;
};

class WarriorCreator : public GameCharacterCreator {
public:
    void chooseClassSpecificOptions() override {
        std::cout << "选择武器类型" << std::endl;
    }
};

class MageCreator : public GameCharacterCreator {
public:
    void chooseClassSpecificOptions() override {
        std::cout << "选择魔法元素" << std::endl;
    }
};

class AssassinCreator : public GameCharacterCreator {
public:
    void chooseClassSpecificOptions() override {
        std::cout << "选择隐匿技能类型" << std::endl;
    }
};

Python示例

from abc import ABC, abstractmethod


class GameCharacterCreator(ABC):
    def create_character(self):
        self.choose_race()
        self.choose_gender()
        self.choose_class_specific_options()

    def choose_race(self):
        print("选择种族")

    def choose_gender(self):
        print("选择性别")

    @abstractmethod
    def choose_class_specific_options(self):
        pass


class WarriorCreator(GameCharacterCreator):
    def choose_class_specific_options(self):
        print("选择武器类型")


class MageCreator(GameCharacterCreator):
    def choose_class_specific_options(self):
        print("选择魔法元素")


class AssassinCreator(GameCharacterCreator):
    def choose_class_specific_options(self):
        print("选择隐匿技能类型")

Go示例

package main

import "fmt"

// 抽象结构体
type GameCharacterCreator struct{}

// 模板方法
func (g *GameCharacterCreator) createCharacter() {
    g.chooseRace()
    g.chooseGender()
    g.chooseClassSpecificOptions()
}

func (g *GameCharacterCreator) chooseRace() {
    fmt.Println("选择种族")
}

func (g *GameCharacterCreator) chooseGender() {
    fmt.Println("选择性别")
}

// 抽象方法,由具体结构体实现
type CharacterCreator interface {
    chooseClassSpecificOptions()
}

type WarriorCreator struct{}

func (w *WarriorCreator) chooseClassSpecificOptions() {
    fmt.Println("选择武器类型")
}

type MageCreator struct{}

func (m *MageCreator) chooseClassSpecificOptions() {
    fmt.Println("选择魔法元素")
}

type AssassinCreator struct{}

func (a *AssassinCreator) chooseClassSpecificOptions() {
    fmt.Println("选择隐匿技能类型")
}

八、模板方法设计模式的优缺点

优点

  • 提高代码复用性
    • 算法的骨架在抽象类中定义一次,多个子类可以复用这个模板方法,减少了代码的重复编写。
  • 可维护性增强
    • 当业务逻辑发生变化时,只需要修改抽象类中的模板方法或者子类中的具体实现,而不需要对整个系统进行大规模的修改。
  • 便于代码的扩展
    • 可以很容易地添加新的子类来实现不同的具体行为,只要遵循抽象类中定义的模板方法结构。

缺点

  • 违反开闭原则
    • 如果要对模板方法中的算法骨架进行修改,可能需要修改抽象类,这就违反了开闭原则(对扩展开放,对修改关闭)。
  • 类层次结构复杂
    • 随着子类的增加,类的层次结构可能会变得比较复杂,导致代码的理解和维护成本增加。

九、模板方法设计模式的升级版

  • 钩子方法(Hook Method)
    • 钩子方法是一种在模板方法模式中常用的扩展机制。它是在抽象类中定义的一个空的或者有默认实现的方法,子类可以选择性地重写这个方法。例如,在泡茶的例子中,可以添加一个钩子方法“isAddSeasoning”,如果子类(某种茶叶)重写这个方法并返回true,那么在模板方法中就会执行添加调料的步骤,否则就跳过这个步骤。
  • 模板方法与策略模式结合
    • 可以将模板方法中的某些抽象方法的实现委托给策略模式中的具体策略类。例如,在游戏角色创建系统中,选择武器类型这个步骤可以使用策略模式,将不同的武器选择策略封装成不同的策略类,然后在战士角色创建子类中通过组合的方式使用这些策略类来实现选择武器类型的抽象方法。这样可以进一步提高代码的灵活性和可维护性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

01 Oracle自学环境搭建(Windows系统)

1 Oracle12C安装 1.1 下载 官网地址&#xff1a;https://www.oracle.com/ 进入官网→Resource→Customer Downloads 如果没有登录&#xff0c;会提示登录后后才能下载 选择适合自己的版本&#xff08;我电脑是Windows系统 64位&#xff09; 选择需要的安装包进行下载 双击下载…

java语法知识(二)

1. class文件可以直接拖动到idea中&#xff0c;显示源码。 2.idea快捷键&#xff1a; sout : System.out.println 输出内容.sout :---》 System.out.println(输出内容); psvm: public static void main() 格式化&#xff1a;ctrl altL 复制粘贴&#xff1a;ctrld 3.注释…

macos python环境安装

安装的问题 mac 安装 python brew install python 通过brew安装的python安装路径 打开终端&#xff0c;输入brew search python&#xff1a; 为啥会被安装在这里 Frameworks 的用途&#xff1a;/Library/Frameworks 是 macOS 系统中专门用于存放框架的目录。框架是一种结构…

计算机网络 笔记 数据链路层 2

1,信道划分&#xff1a; (1)时分复用TDM 将时间等分为“TDM帧”&#xff0c;每个TDM帧内部等分为m个时隙&#xff0c;m个用户对应m个时隙 缺点&#xff1a;每个节点只分到了总带宽的1/m,如果有部分的1节点不发出数据&#xff0c;那么就会在这个时间信道被闲置&#xff0c;利用…

ElasticSearch在Windows环境搭建测试

引子 也持续关注大数据相关内容一段时间&#xff0c;大数据内容很多。想了下还是从目前项目需求侧出发&#xff0c;进行相关学习。Elasticsearch&#xff08;ES&#xff09;是位于 Elastic Stack&#xff08;ELK stack&#xff09; 核心的分布式搜索和分析引擎。Logstash 和 B…

计算机网络 笔记 网络层1

网络层功能概述 主要的任务是把分组从源端传输到目的端&#xff0c;为分组交换网上的不同主句提供通信服务&#xff0c;网络层的传输单位是数据报。 主要的功能&#xff1b; 1&#xff0c;路由选择&#xff1a;路由选择指网络层根据特定算法&#xff0c;为数据包从源节点到目…

Rubyer-WPF:打造优雅、精致的 WPF 用户界面

在桌面应用开发领域&#xff0c;WPF&#xff08;Windows Presentation Foundation&#xff09;凭借其强大的 UI 设计能力和丰富的功能&#xff0c;始终是开发者们青睐的工具之一。今天&#xff0c;我将为大家介绍一款专注于 WPF UI 设计的优秀项目——Rubyer-WPF&#xff0c;它…

设计模式 行为型 访问者模式(Visitor Pattern)与 常见技术框架应用 解析

访问者模式&#xff08;Visitor Pattern&#xff09;是一种行为设计模式&#xff0c;它允许你在不改变元素类的前提下定义作用于这些元素的新操作。这种模式将算法与对象结构分离&#xff0c;使得可以独立地变化那些保存在复杂对象结构中的元素的操作。 假设我们有一个复杂的对…

继承多态语法糖

抽象类(通用) 子类继承父类方法后&#xff0c;这个子类对象如果执行方法的话&#xff0c;只要子类重写了就执行子类的&#xff0c;不执行父类的。 /*** 功能&#xff1a;* 作者&#xff1a;IT伟* 日期&#xff1a;2025/1/13 19:20*/ // 抽象类 A abstract class A {// 构造函…

MarS:一个由生成基础模型驱动的金融市场模拟引擎

“MARS: A FINANCIAL MARKET SIMULATION ENGINE POWERED BY GENERATIVE FOUNDATION MODEL” 项目主页&#xff1a;https://mars-lmm.github.io/ 论文地址&#xff1a;https://arxiv.org/pdf/2409.07486 Github地址&#xff1a;https://github.com/microsoft/MarS/ 摘要 生成…

macOS 使用 FreeRDP 远程访问 Windows:完整指南20250109

&#x1f5a5;️ macOS 使用 FreeRDP 远程访问 Windows&#xff1a;完整指南 引言 随着远程办公需求的快速增长&#xff0c;跨平台远程管理已经成为不可或缺的技能之一。作为一款开源轻量的远程桌面协议实现工具&#xff0c;FreeRDP 为 macOS 用户提供了一个简单、高效的解决…

两分钟解决 :![rejected] master -> master (fetch first) , 无法正常push到远端库

目录 分析问题的原因解决 分析问题的原因 在git push的时候莫名遇到这种情况 若你在git上修改了如README.md的文件。由于本地是没有README.md文件的&#xff0c;所以导致 远端仓库git和本地不同步。 将远端、本地进行合并就可以很好的解决这个问题 注意&#xff1a;直接git pu…

计算机图形学【绘制立方体和正六边形】

工具介绍 OpenGL&#xff1a;一个跨语言的图形API&#xff0c;用于渲染2D和3D图形。它提供了绘制图形所需的底层功能。 GLUT&#xff1a;OpenGL的一个工具库&#xff0c;简化了窗口创建、输入处理和其他与图形环境相关的任务。 使用的函数 1. glClear(GL_COLOR_BUFFER_BIT |…

springboot高校教室资源管理平台

Spring Boot高校教室资源管理平台是一个基于Spring Boot框架开发的高校教室资源管理系统。 一、平台背景与意义 随着高校规模的不断扩大&#xff0c;教室资源的管理变得日益复杂。传统的管理方式往往依赖于人工记录和纸质文件&#xff0c;不仅效率低下&#xff0c;而且容易出…

[笔记] 使用 Jenkins 实现 CI/CD :从 GitLab 拉取 Java 项目并部署至 Windows Server

随着软件开发节奏的加快&#xff0c;持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;已经成为确保软件质量和加速产品发布的不可或缺的部分。Jenkins作为一款广泛使用的开源自动化服务器&#xff0c;为开发者提供了一个强大的平台来实施这些实践。然而…

正点原子STM32F103战舰版电容触摸键学习

一、tpad.h代码 #ifndef __TPAD_H #define __TPAD_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/ /* TPAD 引脚 及 定时器 定义 *//* 我们使用定时器的输入捕获功能, 对TPAD进行检…

JVM:ZGC详解(染色指针,内存管理,算法流程,分代ZGC)

1&#xff0c;ZGC&#xff08;JDK21之前&#xff09; ZGC 的核心是一个并发垃圾收集器&#xff0c;所有繁重的工作都在Java 线程继续执行的同时完成。这极大地降低了垃圾收集对应用程序响应时间的影响。 ZGC为了支持太字节&#xff08;TB&#xff09;级内存&#xff0c;设计了基…

zerox - 使用视觉模型将 PDF 转换为 Markdown

7900 Stars 478 Forks 39 Issues 17 贡献者 MIT License Python 语言 代码: https://github.com/getomni-ai/zerox 主页: OmniAI. Automate document workflows 更多AI开源软件&#xff1a;AI开源 - 小众AI zerox基于视觉模型 API 服务&#xff0c;提供了将 PDF 文档转化为 Mar…

JAVA:Spring Boot 集成 JWT 实现身份验证的技术指南

1、简述 在现代Web开发中&#xff0c;安全性尤为重要。为了确保用户的身份&#xff0c;JSON Web Token&#xff08;JWT&#xff09;作为一种轻量级且无状态的身份验证方案&#xff0c;广泛应用于微服务和分布式系统中。本篇博客将讲解如何在Spring Boot 中集成JWT实现身份验证…

[论文阅读] (35)TIFS24 MEGR-APT:基于攻击表示学习的高效内存APT猎杀系统

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…