设计模式22-迭代器模式

news2024/12/27 10:47:06

设计模式22-迭代器模式

  • 迭代器模式(Iterator Pattern)
  • 动机
  • 定义结构
    • 定义
    • 结构
      • 结构图解释
      • 注意事项
  • C++代码推导
    • 多态属性(虚函数)实现迭代器
      • 1. **返回值问题**
      • 2. **对象切割问题**
      • 3. **内存管理问题**
      • 4. **迭代器生命周期问题**
      • 5. **接口设计问题**
      • 6. **接口返回值的问题**
    • 模版形式实现迭代器
    • 为什么以模版形式实现迭代器而不使用多态性来实现迭代器
      • 1. **性能考虑**
      • 2. **灵活性和类型安全**
      • 3. **类型擦除的避免**
      • 4. **代码复用和泛型编程**
      • 5. **减少内存开销**
      • 6. **模板元编程**
      • 什么时候选择多态性?
      • 总结
  • 优缺点
  • 应用场景
  • 总结

迭代器模式(Iterator Pattern)

动机

  • 软件构建过程集合对象内部结构经常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,也可以让外部客户代码透明的访问其中包含的元素,同时这种透明便利也为同一种算法在多种集合对象上进行操作提供了可能。
  • 使用面向对象技术将这种便利机制抽象为迭代器对象。为应对变化中的集合对象提供了一种优雅的方式。
  • 在软件开发中,经常需要遍历某种集合对象(如数组、链表、树等)中的元素。集合的具体实现通常不应暴露给客户端,以确保集合的封装性和灵活性。为了遍历集合中的元素而不暴露集合的内部结构,迭代器模式引入了一种通用的遍历机制,使得客户端可以遍历集合而不关心集合的内部实现。

定义结构

定义

迭代器模式提供一种方法,顺序访问一个聚合对象中的各个元素,而不暴露该对象的内部表示。

结构

在这里插入图片描述

结构图解释

1. 类和接口

  • Aggregate(聚合类):这是一个集合类,它提供了创建迭代器对象的方法(通常是CreateIterator()或类似的命名)。在这个例子中,Aggregate类有一个Createlterator()方法,该方法返回一个新的迭代器实例。这个迭代器实例是基于当前Aggregate对象创建的,以便能够遍历它的元素。注意,图片中的Createlterator()方法名似乎有一个小错误,应该是CreateIterator(),但我将遵循图片中的实际文本。

  • lterator(迭代器接口):这是一个定义迭代器行为的接口。它声明了用于遍历元素的方法,如First()(定位到序列的第一个元素),Next()(移动到序列中的下一个元素),IsDone()(检查序列中是否还有更多的元素),以及CurrentItem()(获取当前位置的元素)。在标准的命名习惯中,接口名通常是单数且以大写字母开头,但这里使用了lterator,可能是一个打字错误,正确的可能是Iterator

  • Client(客户端类):这是使用迭代器遍历聚合类中的元素的类。它持有一个迭代器对象的引用,并通过这个迭代器来遍历聚合类中的元素。在这个例子中,Client类有一个名为lterator的私有成员变量(可能是Iterator的一个实例,但这里使用了lterator),并使用了迭代器的First()Next()IsDone()方法来遍历元素。

2. 实现类

  • ConcreteAggregate(具体聚合类):这是Aggregate类的一个具体实现,它实现了CreateIterator()方法来返回一个Concretelterator(应为ConcreteIterator)实例。这个实例能够遍历ConcreteAggregate对象中的元素。

  • Concretelterator(具体迭代器类):这是lterator(应为Iterator)接口的一个具体实现。它实现了接口中定义的所有方法,包括First()Next()IsDone()CurrentItem(),以便能够遍历ConcreteAggregate对象中的元素。在这个例子中,Concreteelterator的构造函数接收一个ConcreteAggregate对象作为参数,以便它能够知道要遍历哪个集合。注意,这里的Concretelterator也是一个小错误,正确的应该是ConcreteIterator

注意事项

  • 图片中的类名和方法名中存在一些拼写错误,如lterator应该是IteratorCreatelterator()应该是CreateIterator()Currentitem()应该是CurrentItem()
  • 迭代器模式的核心在于将遍历聚合类对象的责任从聚合类本身转移到迭代器对象上,这有助于保持聚合类的简洁,并使得遍历逻辑可以在不同的迭代器之间重用。

希望这个解释能够帮助您理解这张关于迭代器模式的结构图。

C++代码推导

多态属性(虚函数)实现迭代器

template<typename T>
class Iterator
{
public:
    virtual void first() = 0;
    virtual void next() = 0;
    virtual bool isDone() const = 0;
    virtual T& current() = 0;
};



template<typename T>
class MyCollection{
    
public:
    
    Iterator<T> GetIterator(){
        //...
    }
    
};

template<typename T>
class CollectionIterator : public Iterator<T>{
    MyCollection<T> mc;
public:
    
    CollectionIterator(const MyCollection<T> & c): mc(c){ }
    
    void first() override {
        
    }
    void next() override {
        
    }
    bool isDone() const override{
        
    }
    T& current() override{
        
    }
};

void MyAlgorithm()
{
    MyCollection<int> mc;
    
    Iterator<int> iter= mc.GetIterator();
    
    for (iter.first(); !iter.isDone(); iter.next()){
        cout << iter.current() << endl;
    }
    
}


这种迭代器实现方式存在一些问题,使得它在实际应用中可能并不是最佳选择。以下是一些关键问题及原因:

1. 返回值问题

Iterator<int> iter = mc.GetIterator();
  • 问题Iterator<int>是一个抽象类,不能直接实例化。当你尝试返回一个Iterator<int>对象时,编译器会报错,因为不能创建一个抽象类的实例。

  • 改进:应返回一个指向Iterator<int>的指针或者使用智能指针,例如std::unique_ptr<Iterator<int>>,以确保可以动态地分配一个具体的迭代器实例。

    std::unique_ptr<Iterator<T>> GetIterator() {
        return std::make_unique<CollectionIterator<T>>(*this);
    }
    

2. 对象切割问题

Iterator<int> iter = mc.GetIterator();
  • 问题:即使Iterator<int>能够返回一个对象(假设Iterator<int>不是抽象类),由于Iterator<int>的拷贝会发生对象切割问题,导致派生类的部分(CollectionIterator<int>的特定实现)丢失,剩下的只是一个基类的对象,这会使得运行时的多态行为失效。

  • 改进:避免对象切割,返回迭代器的指针或引用。

3. 内存管理问题

  • 问题:如果使用原始指针(如Iterator<T>*)返回具体迭代器,会面临内存管理问题。如果用户忘记释放内存,可能会导致内存泄漏。

  • 改进:使用智能指针来管理内存,例如std::unique_ptrstd::shared_ptr。智能指针会自动管理对象的生命周期,避免内存泄漏。

4. 迭代器生命周期问题

  • 问题CollectionIterator持有MyCollection的副本(通过成员变量mc)。这意味着迭代器中保存的集合与原集合可能是两个不同的对象。如果集合发生改变,迭代器将无法反映这些变化。

  • 改进:迭代器应持有集合的引用而不是副本,以确保它们始终引用相同的集合对象。

    template<typename T>
    class CollectionIterator : public Iterator<T> {
        const MyCollection<T>& mc;
    public:
        CollectionIterator(const MyCollection<T>& c) : mc(c) { }
        // 实现迭代器方法
    };
    

5. 接口设计问题

  • 问题:迭代器接口设计可能存在一些细节问题。例如,current()方法返回对当前元素的引用时,如果集合为空,调用current()会导致未定义行为。

  • 改进:在实现current()时,应该确保集合非空或抛出异常以避免未定义行为。也可以提供一个安全的接口,如返回指向元素的指针。

6. 接口返回值的问题

  • 问题GetIterator()方法在你的设计中返回Iterator<T>,但是实际应该返回指向具体Iterator的指针或引用,以支持多态。

  • 改进:改用返回智能指针或指针以支持多态:

    std::unique_ptr<Iterator<T>> GetIterator() {
        return std::make_unique<CollectionIterator<T>>(*this);
    }
    

模版形式实现迭代器

以下是一个使用迭代器模式遍历一个简单集合(例如整数数组)的C++代码示例。

迭代器接口类:

template <typename T>
class Iterator {
public:
    virtual ~Iterator() {}
    virtual T first() = 0;
    virtual T next() = 0;
    virtual bool isDone() const = 0;
    virtual T currentItem() const = 0;
};

具体迭代器类:

template <typename T>
class ConcreteIterator : public Iterator<T> {
private:
    const std::vector<T>& collection;
    size_t currentIndex;

public:
    ConcreteIterator(const std::vector<T>& collection)
        : collection(collection), currentIndex(0) {}

    T first() override {
        return collection[0];
    }

    T next() override {
        currentIndex++;
        if (currentIndex < collection.size()) {
            return collection[currentIndex];
        }
        throw std::out_of_range("Iterator out of range");
    }

    bool isDone() const override {
        return currentIndex >= collection.size();
    }

    T currentItem() const override {
        if (currentIndex < collection.size()) {
            return collection[currentIndex];
        }
        throw std::out_of_range("Iterator out of range");
    }
};

聚合接口类:

template <typename T>
class Aggregate {
public:
    virtual ~Aggregate() {}
    virtual Iterator<T>* createIterator() const = 0;
};

具体聚合类:

template <typename T>
class ConcreteAggregate : public Aggregate<T> {
private:
    std::vector<T> items;

public:
    ConcreteAggregate(std::initializer_list<T> elements) : items(elements) {}

    Iterator<T>* createIterator() const override {
        return new ConcreteIterator<T>(items);
    }

    size_t size() const {
        return items.size();
    }

    T getItem(size_t index) const {
        if (index < items.size()) {
            return items[index];
        }
        throw std::out_of_range("Index out of range");
    }
};

客户端代码:

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    ConcreteAggregate<int> aggregate({1, 2, 3, 4, 5});
    Iterator<int>* iterator = aggregate.createIterator();

    for (iterator->first(); !iterator->isDone(); iterator->next()) {
        std::cout << iterator->currentItem() << " ";
    }

    delete iterator;

    return 0;
}

运行结果:

1 2 3 4 5

为什么以模版形式实现迭代器而不使用多态性来实现迭代器

在 C++ 中,实现迭代器时选择使用模板而非多态性(即虚函数)主要有以下几个原因:

1. 性能考虑

模板是在编译时进行类型替换的,这意味着模板代码在编译时生成特定类型的代码实例,而不需要在运行时进行类型检查或虚函数调用。这带来了以下几个性能上的好处:

  • 无虚函数开销:使用模板可以避免虚函数调用的开销。虚函数调用通常涉及通过虚函数表(vtable)间接访问函数,可能导致一些额外的性能开销。而模板直接生成目标类型的代码,函数调用可以内联,从而提高运行时性能。

  • 优化机会:编译器在模板实例化时有更多的优化机会。由于编译器知道确切的类型,可以进行更多的优化,比如内联、消除不必要的操作等。

2. 灵活性和类型安全

  • 强类型检查:模板提供了更强的类型安全性,因为所有类型信息在编译时就已经确定。模板迭代器在编译时会检查是否所有操作都符合类型约束,避免了在运行时发生类型错误。

  • 支持更多容器:模板允许你为不同类型的集合(如std::vector<T>std::list<T>等)生成不同的迭代器实现,而不需要定义多个派生类。这样,模板迭代器可以更好地适应各种容器类型,而不需要为每种类型手动定义派生类。

3. 类型擦除的避免

  • 避免类型擦除:在多态性实现中,为了支持不同类型的对象,通常需要使用指针或引用来指向基类。这涉及到类型擦除(type erasure),即在运行时丢失部分类型信息。而模板不会丢失类型信息,能够保留完整的类型信息,并在编译时确保类型的正确性。

4. 代码复用和泛型编程

  • 代码复用:模板允许编写泛型代码,这样可以避免重复为不同类型编写类似的代码。通过使用模板,可以编写一次泛型迭代器,然后在不同类型的集合上复用该迭代器代码。

  • 泛型编程的自然适应性:模板是一种强大的工具,尤其适用于泛型编程。在 C++ 标准库中,几乎所有容器的迭代器都是通过模板实现的,形成了一个一致的泛型编程体系(如 STL)。这使得模板迭代器可以无缝地与 C++ 标准库的算法(如 std::sort, std::find 等)协同工作。

5. 减少内存开销

  • 避免额外的对象分配:使用多态性时,通常需要动态分配内存以存储不同类型的对象,这会增加内存开销。模板迭代器由于不需要动态分配内存,可以在栈上分配,并且避免了额外的对象开销。

6. 模板元编程

  • 支持高级编程技巧:模板不仅仅是类型安全和高效的,它还支持复杂的模板元编程技巧。这种能力允许在编译时执行计算和生成代码,从而进一步优化代码的执行效率。

什么时候选择多态性?

尽管模板具有很多优势,但在某些情况下,多态性(即使用虚函数)仍然是合适的选择:

  • 需要在运行时处理异构集合:如果需要在运行时处理不同类型的对象(如一个集合中包含不同类型的对象),模板可能无法提供直接的支持,此时使用多态性可以更好地处理这些需求。

  • 接口稳定性:如果需要提供一个稳定的接口供外部使用,而不希望外部依赖模板实现(模板通常定义在头文件中,容易暴露实现细节),则可以选择使用虚函数接口来隐藏实现。

总结

在 C++ 中,模板实现迭代器的方式由于性能、类型安全性、灵活性、和代码复用性等方面的优势,通常是优于使用多态性的。模板通过在编译时确定类型和生成代码,避免了运行时开销,使得迭代器更高效且灵活。但在处理需要运行时多态性或异构集合的情况下,多态性仍然是不可替代的。

优缺点

优点:

  1. 一致的遍历接口:迭代器模式为不同的聚合结构提供了一致的遍历接口,使得遍历操作与聚合对象的实现解耦。
  2. 封装性:迭代器模式可以避免暴露聚合对象的内部表示,保持了集合对象的封装性。
  3. 灵活性:可以为同一个聚合对象创建不同的迭代器,以支持不同的遍历方式(如正向、反向遍历)。

缺点:

  1. 可能增加复杂性:引入了额外的迭代器类,可能会增加系统的复杂性,特别是当需要支持多种迭代方式时。
  2. 对象数量增加:对于大型聚合对象,迭代器的创建和管理可能会导致较多的对象实例,增加内存开销。

应用场景

迭代器模式适用于以下场景:

  1. 需要遍历集合对象:如数组、链表、树形结构等,需要对集合对象中的元素进行遍历时。
  2. 需要多种遍历方式:如正序、倒序或特定条件下的遍历,可以为同一个聚合对象定义不同的迭代器。
  3. 隐藏聚合对象的实现细节:希望客户端在遍历集合时无需了解集合的具体实现(如内部数据结构)时。

总结

  • 有些模式运用的技术机制可能会过时但是它的思想不会过时
  • 迭代抽象:访问一个聚合对象的内容,而无需暴露它的内部表示。
  • 迭代多态:在遍历不同的集合结构提供一个统一的接口。从而支持同样的算法在不同的集合结构上进行操作。
  • 迭代器的健壮性考虑:便利的同时更改迭代器所在的集合结构会导致问题。
  • 迭代器模式为集合对象提供了一种通用的遍历接口,使得客户端可以以一致的方式访问集合中的元素,而不需要了解集合的内部结构。通过封装遍历逻辑,迭代器模式有效地解耦了集合的实现和使用,但也可能引入额外的复杂性和开销。在需要对复杂集合进行多种遍历时,迭代器模式特别有用。

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

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

相关文章

static、extern,const关键字

1、static关键字 static关键字&#xff1a;延长生命周期&#xff0c;限制作用域 static修饰局部变量&#xff1a;静态局部变量 static修饰全局变量&#xff1a;静态全局变量 static修饰函数&#xff1a;静态函数 2、extern关键字 extern&#xff1a;引用其他文件 .c 中的全局…

对敲期权组合如何操作?

对敲期权组合按照你说的对沖敲出期权应该是一种期权套利行为&#xff0c;在买入的同时卖出一个执行价格不同的期权进行对冲&#xff0c;或者在卖出一张期权合约的时候同时买进一张执行价不动的同类期权进行对中&#xff0c;这样亏报有限&#xff0c;是种套利行为&#xff0c;下…

Java基础之进制转换

1 进制基础 概念&#xff1a; ​ 进制就是进位制&#xff0c;是人们规定的一种进位方法&#xff0c;二进制逢2进1&#xff0c;八进制是逢8进1&#xff0c;十进制逢10进1&#xff0c;十六进制逢16进1。 不同进制形式&#xff1a; 二进制 0b或0B开头&#xff0c;由0和1组成 八…

爬虫配置代理:保护隐私有效地抓取数据

爬虫配置代理的详细指南 在进行网络爬虫时&#xff0c;使用代理可以帮助我们更有效地抓取数据&#xff0c;避免IP被封禁&#xff0c;并提高隐私保护。本文将详细介绍如何在爬虫中配置代理&#xff0c;包括不同的代理类型、如何选择合适的代理以及在Python中实现代理的具体步骤…

中国软件评测中心:2024年最新人工智能大语言模型技术发展研究报告 (附文档)

人工智能作为引领新一轮科技产业革命的战略性技术和新质生产力重要驱动力&#xff0c;正在引发经济、社会、文化等领域的变革和重塑&#xff0c;2023 年以来&#xff0c;以 ChatGPT、GPT-4 为代表的大模型技术的出台&#xff0c;因其强大的内容生成及多轮对话能力&#xff0c;引…

python-A+B again

[题目描述] 小理有一个非常简单的问题给你&#xff0c;给你两个整数 A 和 B&#xff0c;你的任务是计算 AB。输入格式&#xff1a; 输入共 2∗T1 行。 输入的第一行包含一个整数 T 表示测试实例的个数&#xff0c;然后 2∗T 行&#xff0c;分别表示 A 和 B 两个正整数。注意整数…

调研在深度学习中如何读代码

这里调研了四个up主的内容&#xff0c;对他们讲的内容摘了一下主要的内容。想要看原文的画可以看原篇。 1.如何学习别人的代码&#xff08;代码量较大时&#xff09;_怎么学习别人的代码-CSDN博客 想要掌握的好&#xff0c;光阅读是不够的&#xff0c;一定要动手写、训练模型…

k8s 部署RuoYi-Vue-Plus之minio搭建

1.直接部署一个pod 需要挂载存储款, 可参考 之前文章设置 https://blog.csdn.net/weimeibuqieryu/article/details/140183843 2.部署yaml 创建部署文件 minio-deploy.yaml apiVersion: v1 kind: PersistentVolume metadata:name: minio-pvnamespace: ruoyi #使用ns ruoyi s…

MyCAT读写分离实现

1. 添加一个新的虚拟主机&#xff0c;设置ip为10.1.1.60,主机名为 mycat.yuanyu.zhangmin.关闭防火墙 SELinux NetworkManager 2. 上传jdk和mycat安装包 3. 解压并且添加到指定的位置 4. 查看并且配置jdk环境 、5. 测试启动myca就可以了 6. 找到server.xml和schema.xml 7. 配…

Python酷库之旅-第三方库Pandas(081)

目录 一、用法精讲 336、pandas.Series.str.rpartition方法 336-1、语法 336-2、参数 336-3、功能 336-4、返回值 336-5、说明 336-6、用法 336-6-1、数据准备 336-6-2、代码示例 336-6-3、结果输出 337、pandas.Series.str.slice方法 337-1、语法 337-2、参数 …

RCE---eval长度限制绕过技巧

目录 题目源码 方法一&#xff1a;命令执行的利用 方法二&#xff1a;file_put_contents&#xff08;本地文件包含的利用&#xff09; 方法三&#xff1a;usort(…$_GET); 题目源码 <?php $param $_REQUEST[param]; if(strlen($param)<17 && stripos($par…

【题解】【一题多解】—— [NOIP1998 普及组] 三连击

【题解】—— [NOIP1998 普及组] 三连击 [NOIP1998 普及组] 三连击题目背景题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示 解法1.直接提交答案解法2.普通枚举2.1.题意分析2.2.AC代码 解法3.全排列枚举3.1.题意分析3.2.AC代码 解法4.深度优先搜索4.1.题意分析4.2.AC…

宝塔面板屏蔽 Censys,防止源站 IP 泄露

Censys 搜索引擎很强大。Censys 每天都会扫描 IPv4 地址空间&#xff0c;以搜索所有联网设备并收集相关的信息&#xff0c;并返回一份有关资源&#xff08;如设备、网站和证书&#xff09;配置和部署信息的总体报告。 在 IP 前加上 https 访问时&#xff0c;Nginx 会自动返回该…

嵌入式学习 20(Linux高级编程——文件——misc)

文件操作相关函数 一、symlink 函数 int symlink(const char *oldpath, const char *newpath); 功能&#xff1a; 创建一个指向 oldpath 文件的新的符号链接&#xff08;软链接&#xff09;文件。 参数&#xff1a; • oldpath&#xff1a;被链接指向的原始文件的路径。 • …

spring事务失效问题可以这样解决

今天咱们就来聊聊在Spring事务管理中你可能踩过的坑&#xff0c;并教你如何规避这些陷阱。 事务提交的陷阱 有时&#xff0c;事务方法在抛出异常后没有回滚&#xff0c;而是被提交了。 这通常是由于异常被捕获但没有显式抛出&#xff0c;导致Spring误以为事务正常完成。 错…

Review Learning : 推进一体化超高清图像恢复训练方法

Review Learning: Advancing All-in-One Ultra-High-Definition Image Restoration Training Method 摘要 一体化图像恢复任务变得越来越重要&#xff0c;特别是对于超高清&#xff08;UHD&#xff09;图像。 现有的一体机UHD图像恢复方法通常通过引入针对不同退化类型的即时…

小区团购管理

TOC springboot254小区团购管理 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广泛运用&…

一文搞懂后端面试之数据库综合应用【中间件 | 数据库 | MySQL | 高可用 | 高性能】

查询缓存 在MySQL里面&#xff0c;允许用户开启查询缓存。你可以理解这个缓存就是用SQL作为键&#xff0c;而对应的查询结果集就是值。如果下次过来的还是同一个查询&#xff0c;那么就直接返回缓存起来的查询结果集。 但是查询缓存不一定带来查询性能提升。如果你的查询每一…

接口隔离原则

接口隔离原则 接口隔离原则就是客户端不应该依赖它不需要的接口&#xff0c;或者说类间的依赖关系应该建立在最小的接口上。 我们以搜索美女为例&#xff0c;设计了如下的类图&#xff1a; 源代码如下。美女及其实现类&#xff1a; 搜索程序及其子类源代码如下&#xff1a; 最…

0101中文乱码-BufferedImage-图片处理

文章目录 1、问题描述2 、电子证书生成3、中文乱码原因及修复4、思考 结语 1、问题描述 接手维护一个休闲赛事类项目&#xff0c;因为服务器到期&#xff0c;项目从云服务器迁移到本地服务器。 项目生成比赛&#xff0c;分为二人组、三人组等等的团体&#xff1b;比赛设置几个…