C++设计模式(18)——模板方法模式

news2025/1/22 16:16:01

亦称: Template Method

意图

模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
在这里插入图片描述

问题

假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式 (PDF、 DOC 或 CSV) 的文档, 程序则会试图从这些文件中抽取有意义的数据, 并以统一的格式将其返回给用户。

该程序的首个版本仅支持 DOC 文件。 在接下来的一个版本中, 程序能够支持 CSV 文件。 一个月后, 你 “教会” 了程序从 PDF 文件中抽取数据。
在这里插入图片描述
数据挖掘类中包含许多重复代码。

一段时间后, 你发现这三个类中包含许多相似代码。 尽管这些类处理不同数据格式的代码完全不同, 但数据处理和分析的代码却几乎完全一样。 如果能在保持算法结构完整的情况下去除重复代码, 这难道不是一件很棒的事情吗?

还有另一个与使用这些类的客户端代码相关的问题: 客户端代码中包含许多条件语句, 以根据不同的处理对象类型选择合适的处理过程。 如果所有处理数据的类都拥有相同的接口或基类, 那么你就可以去除客户端代码中的条件语句, 转而使用多态机制来在处理对象上调用函数。

解决方案

模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在 “模板方法” 中依次调用这些方法。 步骤可以是 抽象的, 也可以有一些默认的实现。 为了能够使用算法, 客户端需要自行提供子类并实现所有的抽象步骤。 如有必要还需重写一些步骤 (但这一步中不包括模板方法自身)。

让我们考虑如何在数据挖掘应用中实现上述方案。 我们可为图中的三个解析算法创建一个基类, 该类将定义调用了一系列不同文档处理步骤的模板方法。
在这里插入图片描述
模板方法将算法分解为步骤, 并允许子类重写这些步骤, 而非重写实际的模板方法。

首先, 我们将所有步骤声明为 抽象类型, 强制要求子类自行实现这些方法。 在我们的例子中, 子类中已有所有必要的实现, 因此我们只需调整这些方法的签名, 使之与超类的方法匹配即可。

现在, 让我们看看如何去除重复代码。 对于不同的数据格式, 打开和关闭文件以及抽取和解析数据的代码都不同, 因此无需修改这些方法。 但分析原始数据和生成报告等其他步骤的实现方式非常相似, 因此可将其提取到基类中, 以让子类共享这些代码。

正如你所看到的那样, 我们有两种类型的步骤:

1、抽象步骤必须由各个子类来实现
2、可选步骤已有一些默认实现, 但仍可在需要时进行重写

还有另一种名为钩子的步骤。 钩子是内容为空的可选步骤。 即使不重写钩子, 模板方法也能工作。 钩子通常放置在算法重要步骤的前后, 为子类提供额外的算法扩展点。

真实世界类比

在这里插入图片描述
可对典型的建筑方案进行微调以更好地满足客户需求。
模板方法可用于建造大量房屋。 标准房屋建造方案中可提供几个扩展点, 允许潜在房屋业主调整成品房屋的部分细节。

每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同。

模板方法模式结构

在这里插入图片描述
1、抽象类 (Abstract­Class) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。

2、具体类 (Concrete­Class) 可以重写所有步骤, 但不能重写模板方法自身。

伪代码

本例中的模板方法模式为一款简单策略游戏中人工智能的不同分支提供 “框架”。
在这里插入图片描述
一款简单游戏的 AI 类。

游戏中所有的种族都有几乎同类的单位和建筑。 因此你可以在不同的种族上复用相同的 AI 结构, 同时还需要具备重写一些细节的能力。 通过这种方式, 你可以重写半兽人的 AI 使其更富攻击性, 也可以让人类侧重防守, 还可以禁止怪物建造建筑。 在游戏中新增种族需要创建新的 AI 子类, 还需要重写 AI 基类中所声明的默认方法。

// 抽象类定义了一个模板方法,其中通常会包含某个由抽象原语操作调用组成的算
// 法框架。具体子类会实现这些操作,但是不会对模板方法做出修改。
class GameAI is
    // 模板方法定义了某个算法的框架。
    method turn() is
        collectResources()
        buildStructures()
        buildUnits()
        attack()

    // 某些步骤可在基类中直接实现。
    method collectResources() is
        foreach (s in this.builtStructures) do
            s.collect()

    // 某些可定义为抽象类型。
    abstract method buildStructures()
    abstract method buildUnits()

    // 一个类可包含多个模板方法。
    method attack() is
        enemy = closestEnemy()
        if (enemy == null)
            sendScouts(map.center)
        else
            sendWarriors(enemy.position)

    abstract method sendScouts(position)
    abstract method sendWarriors(position)

// 具体类必须实现基类中的所有抽象操作,但是它们不能重写模板方法自身。
class OrcsAI extends GameAI is
    method buildStructures() is
        if (there are some resources) then
            // 建造农场,接着是谷仓,然后是要塞。

    method buildUnits() is
        if (there are plenty of resources) then
            if (there are no scouts)
                // 建造苦工,将其加入侦查编组。
            else
                // 建造兽族步兵,将其加入战士编组。

    // ……

    method sendScouts(position) is
        if (scouts.length > 0) then
            // 将侦查编组送到指定位置。

    method sendWarriors(position) is
        if (warriors.length > 5) then
            // 将战斗编组送到指定位置。

// 子类可以重写部分默认的操作。
class MonstersAI extends GameAI is
    method collectResources() is
        // 怪物不会采集资源。

    method buildStructures() is
        // 怪物不会建造建筑。

    method buildUnits() is
        // 怪物不会建造单位。

模板方法模式适合应用场景

当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。

模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。

当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。

在将算法转换为模板方法时, 你可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。

实现方式

1、分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同。

2、创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final最终修饰模板方法以防止子类对其进行重写。

3、虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法。

4、可考虑在算法的关键步骤之间添加钩子。

5、为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。

模板方法模式优缺点

在这里插入图片描述

与其他模式的关系

1、工厂方法模式模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

2、模板方法基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略模式基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

C++ 模板方法模式讲解和代码示例

模版方法是一种行为设计模式, 它在基类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
使用示例: 模版方法模式在 C++ 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。

识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。

概念示例

本例说明了模板方法设计模式的结构并重点回答了下面的问题:

它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

/**
 * The Abstract Class defines a template method that contains a skeleton of some
 * algorithm, composed of calls to (usually) abstract primitive operations.
 *
 * Concrete subclasses should implement these operations, but leave the template
 * method itself intact.
 */
class AbstractClass {
  /**
   * The template method defines the skeleton of an algorithm.
   */
 public:
  void TemplateMethod() const {
    this->BaseOperation1();
    this->RequiredOperations1();
    this->BaseOperation2();
    this->Hook1();
    this->RequiredOperation2();
    this->BaseOperation3();
    this->Hook2();
  }
  /**
   * These operations already have implementations.
   */
 protected:
  void BaseOperation1() const {
    std::cout << "AbstractClass says: I am doing the bulk of the work\n";
  }
  void BaseOperation2() const {
    std::cout << "AbstractClass says: But I let subclasses override some operations\n";
  }
  void BaseOperation3() const {
    std::cout << "AbstractClass says: But I am doing the bulk of the work anyway\n";
  }
  /**
   * These operations have to be implemented in subclasses.
   */
  virtual void RequiredOperations1() const = 0;
  virtual void RequiredOperation2() const = 0;
  /**
   * These are "hooks." Subclasses may override them, but it's not mandatory
   * since the hooks already have default (but empty) implementation. Hooks
   * provide additional extension points in some crucial places of the
   * algorithm.
   */
  virtual void Hook1() const {}
  virtual void Hook2() const {}
};
/**
 * Concrete classes have to implement all abstract operations of the base class.
 * They can also override some operations with a default implementation.
 */
class ConcreteClass1 : public AbstractClass {
 protected:
  void RequiredOperations1() const override {
    std::cout << "ConcreteClass1 says: Implemented Operation1\n";
  }
  void RequiredOperation2() const override {
    std::cout << "ConcreteClass1 says: Implemented Operation2\n";
  }
};
/**
 * Usually, concrete classes override only a fraction of base class' operations.
 */
class ConcreteClass2 : public AbstractClass {
 protected:
  void RequiredOperations1() const override {
    std::cout << "ConcreteClass2 says: Implemented Operation1\n";
  }
  void RequiredOperation2() const override {
    std::cout << "ConcreteClass2 says: Implemented Operation2\n";
  }
  void Hook1() const override {
    std::cout << "ConcreteClass2 says: Overridden Hook1\n";
  }
};
/**
 * The client code calls the template method to execute the algorithm. Client
 * code does not have to know the concrete class of an object it works with, as
 * long as it works with objects through the interface of their base class.
 */
void ClientCode(AbstractClass *class_) {
  // ...
  class_->TemplateMethod();
  // ...
}

int main() {
  std::cout << "Same client code can work with different subclasses:\n";
  ConcreteClass1 *concreteClass1 = new ConcreteClass1;
  ClientCode(concreteClass1);
  std::cout << "\n";
  std::cout << "Same client code can work with different subclasses:\n";
  ConcreteClass2 *concreteClass2 = new ConcreteClass2;
  ClientCode(concreteClass2);
  delete concreteClass1;
  delete concreteClass2;
  return 0;
}

Output.txt: 执行结果

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway

来源:https://refactoringguru.cn/design-patterns/template-method
仅供学习,非商业用途,侵删

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

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

相关文章

ICRA 2023 | 首个联合暗光增强和深度估计的自监督方法STEPS

原文链接&#xff1a;https://www.techbeat.net/article-info?id4629 作者&#xff1a;郑宇鹏 本文中&#xff0c;我们提出了STEPS&#xff0c;第一个自监督框架来联合学习图像增强和夜间深度估计的方法。它可以同时训练图像增强网络和深度估计网络&#xff0c;并利用了图像增…

【JAVA程序设计】(C00103)基于Springboot+Thymeleaf智能分类的相册管理系统——有文档

基于SpringbootThymeleaf智能分类的相册管理系统——有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于SpringbootThymeleaf智能分类的相册管理系统共分为二个角色&#xff1a;系统管理员、用户 管理员角色包含以下功能&#xff1a; 登录、用户管理&#xff08;增…

LeetCode-131. 分割回文串

目录题目思路回溯题目来源 131. 分割回文串 题目思路 切割问题类似组合问题。 例如对于字符串abcdef&#xff1a; 组合问题&#xff1a;选取一个a之后&#xff0c;在bcdef中再去选取第二个&#xff0c;选取b之后在cdef中再选取第三个…。切割问题&#xff1a;切割一个a之后&…

现在的00后,实在是太卷了

现在的小年轻真的卷得过分了。前段时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条…

ES mapping 详解

nested 类型&#xff1f;&#xff1f;&#xff1f; _all _routing; ES-mapping Elasticsearch根据业务创建映射mapping结构分析&#xff1a;keyword和text&#xff08;一&#xff09;_elasticsearch keyword mapping_周全全的博客-CSDN博客 0.Mapping样例 {"mapping…

【微信小程序】--JSON 配置文件作用(三)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#…

【Vue源码解析】Vue虚拟dom和diff算法

Vue虚拟dom和diff算法1. 简介2. 搭建环境1. 安装snabbdom2. 安装webpack5并配置3、函数3.1 虚拟节点vnode的属性3.2 使用h函数 创建虚拟节点3.3 使用patch函数 将虚拟节点上DOM树3.4 h函数嵌套使用&#xff0c;得到虚拟DOM树&#xff08;重要&#xff09;3.5 patchVnode函数3.6…

Mac 上搭建 iOS WebDriverAgent 环境

文章目录Mac环境搭建配置 Xcode 生成 WDA常见问题brew 安装失败Mac环境搭建 macOS 系统电脑&#xff1a;12.6.2 Xcode&#xff1a;14.0.1&#xff08;xcodebuild -version&#xff09; appium Desktop&#xff1a;1.21.0 (下载链接) Appium Desktop 1.22.0 &#xff0c;从该版…

Redis 高级数据类型

文章目录一、Bitmaps&#xff1a;属性状态统计二、HyperLogLog&#xff1a;基数统计三、GEO&#xff1a;地理位置信息计算提示&#xff1a;以下是本篇文章正文内容&#xff0c;Redis系列学习将会持续更新 一、Bitmaps&#xff1a;属性状态统计 Bitmaps类型&#xff1a; 统计一…

数据结构:循环队列的实现(leetcode622.设计循环队列)

目录 一.循环队列简单介绍 二.用静态数组实现循环队列 1.数组循环队列结构设计 2.数组循环队列的堆区内存申请接口 3.数据出队和入队的接口实现 4.其他操作接口 5.数组循环队列的实现代码总览 三.静态单向循环链表实现循环队列 1.链表循环队列的结构设计 2.创建静…

Nginx第二讲

目录 二、Nginx02 2.1 keepalived和heartbeat介绍 2.1.1 两者的介绍 2.1.2 keepalived简介 2.1.3 VRRP协议与工作原理 2.1.4 Keepalvied的工作原理 2.2 安装环境及keepalived 2.3 启动与验证keepalived 2.4 keepalived测试 2.4.1 环境准备 2.4.2 配置keepalived 2.…

守护最后一道防线:Coremail邮件安全网关推出邮件召回功能

根据Coremail邮件安全大数据中心2022年Q4季报显示&#xff0c;2021年CAC识别钓鱼邮件1.81亿&#xff0c;2022年上升至2.25亿&#xff0c;增幅高达24.1%。 这表明2022年平均每天有61万7088封钓鱼邮件被接收及发出&#xff0c;企业用户面临潜在经济损失不可估量。 尤其是活跃至今…

Linux中安装JDK

Linux中安装JDK一 、下载JDK包1、下载网址2、往下翻&#xff0c;找到 java83、继续往下翻找到要下载的版本 64位linux版本二 上传jdk安装包三 开始安装整体过程1、解压文件2、查看解压文件3、进入解压文件夹确认4、配置环境变量5、重新加载环境变量6、确认安装成功一 、下载JDK…

Leetcode11. 盛最多水的容器

一、题目描述&#xff1a; 给定一个长度为 nnn 的整数数组 heightheightheight 。有 nnn 条垂线&#xff0c;第 iii 条线的两个端点是 (i,0)(i, 0)(i,0) 和 (i,height[i])(i, height[i])(i,height[i]) 。 找出其中的两条线&#xff0c;使得它们与 xxx 轴共同构成的容器可以容…

【C++】类与对象(引入)

目录 前言 类的引入 类的定义 封装与访问限定符 封装 访问限定符 类的实例化 类的大小 this指针 特性 前言 &#x1f3b6;我们都知道&#xff0c;C语言是面向过程的编程&#xff0c;而C是面向对象的编程&#xff0c;更多体现在编程的关注点上。 &#x1f3b6;就拿洗…

ES6--class类(详解/看完必会)

目录 1、基本概念 2、基本用法 3、class与构造函数的区别 4、constructor的使用 5、自定义方法 6、extends和super &#xff08;1&#xff09;问题一&#xff1a;我们想要在点击按钮二的时候改变字体大小&#xff0c;如何写呢&#xff1f; &#xff08;2&#xff09;问…

【极海APM32替代笔记】低功耗模式配置及配置汇总

【极海APM32替代笔记】低功耗模式配置及配置汇总 文章总结&#xff1a;&#xff08;后续更新以相关文章为准&#xff09; 【STM32笔记】低功耗模式、WFI命令等进入不了休眠的可能原因&#xff08;系统定时器SysTick一直产生中断&#xff09; 【STM32笔记】HAL库低功耗模式配置…

万字长文解析!复现和使用GPT-3/ChatGPT,你所应该知道的

关于作者 英文原版作者&#xff1a;杨靖锋&#xff0c;现任亚马逊科学家&#xff0c;本科毕业于北大&#xff0c;硕士毕业于佐治亚理工学院&#xff0c;师从 Stanford 杨笛一教授。 杨昊桐 译&#xff0c;王骁 修订 感谢靳弘业对第一版稿件的建议&#xff0c;感谢陈三星&am…

timm使用swin-transformer

1.安装 pip install timm2.timm中有多少个预训练模型 #timm中有多少个预训练模型 model_pretrain_list timm.list_models(pretrainedTrue) print(len(model_pretrain_list), model_pretrain_list[:3])3加载swin模型一般准会出错 model_ft timm.create_model(swin_base_pat…

Head First设计模式---4.工厂方法模式

2.1工厂方法模式 亦称&#xff1a; 虚拟构造函数、Virtual Constructor、Factory Method 工厂方法模式是一种创建型设计模式&#xff0c; 其在父类中提供一个创建对象的方法&#xff0c; 允许子类决定实例化对象的类型。 [外链图片转存失败,源站可能有防盗链机制,建议将图片…