首页 > 其他分享 >迭代器模式:如何实现遍历数据时的职责分离?

迭代器模式:如何实现遍历数据时的职责分离?

时间:2024-09-22 16:23:33浏览次数:9  
标签:遍历 职责 迭代 topics Topic 集合 public

迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中,迭代器已经成为一个基础的类库,直接用来遍历集合对象。在平时开发中,我们更多的是直接使用它,很少会从零去实现一个迭代器。今天,我们就一起来看看迭代器模式的原理和实现,帮助大家更好理解。

一、模式原理分析

迭代器模式又叫游标(Cursor)模式,它的原始定义是:迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。

可以看到,该定义很明确地指出,迭代器模式就是为了提供一种通用的访问对象的方式。我们先来看下它的 UML 图: 从该 UML 图中,我们能看出迭代器模式的四个关键角色。

  • 抽象集合类(Aggregate):创建和抽象迭代器类相关联的方法,同时可以添加其他集合类需要的方法。

  • 具体集合类(ConcreteAggregate):实现抽象集合类声明的所有方法,在具体使用集合类时会创建对应具体的迭代器类。

  • 抽象迭代器类(Iterator):定义统一的迭代器方法 hasNext() 和 next(),用于判断当前集合中是否还有对象以及按顺序读取集合中的当前对象。

  • 具体迭代器类(ConcreteIterator):实现了抽象迭代器类声明的方法,处理具体集合中对对象位置的偏移以及具体对象数据的传输。

UML 图对应的代码实现如下:

public interface Iterator{
    Object next();
    boolean hasNext();
}
public class ConcreteIterator implements Iterator {
    private Object[] objects;
    private int position;
    public ConcreteIterator(Object[] objects) {
        this.objects = objects;
    }
    @Override
    public Object next() {
        return objects[position++];
    }
    @Override
    public boolean hasNext() {
        if(position >= objects.length) {
            return false;
        }
        return true;
    }
}
public interface Aggregate {
    Iterator createIterator();
}
public class ConcreteAggregate implements Aggregate{
    private Object[] objects;
    public ConcreteAggregate(Object[] objects) {
        this.objects = objects;
    }
    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(objects);
    }
}
public class Demo {
    public static void main(String[] args) {
        Object[] objects = new Object[2];
        objects[0] = new Object();
        objects[1] = new Object();
        Aggregate aggregate = new ConcreteAggregate(objects);
        Iterator iterator = aggregate.createIterator();
        while(iterator.hasNext()) {
            Object currentObject = iterator.next();
            System.out.println(currentObject.toString());
        }
    }
}
//输出结果:
java.lang.Object@7ea987ac
java.lang.Object@12a3a380

从最后的结果可以看出,我们通过实现迭代器模式最终打印了对象数组中的两个不同对象。从上面的代码实现也能看出,迭代器的实现原理非常简单,就是通过为集合对象创建统一的迭代器 Iterator 来统一对集合里的对象进行访问

二、使用场景分析

一般来讲,迭代器模式常见的使用场景有以下几种。

  • 希望对客户端隐藏其遍历算法复杂性时。有时可能出于使用便利性或安全性的考虑,只想让客户端使用遍历某个集合的对象,而不告诉客户端具体的遍历算法。

  • 需要简化重复的循环遍历逻辑时。比如,读取数组里的数据,遍历二叉树等树形结构。

为了更好地理解迭代器模式,下面我们还是通过一个简单的例子来为你说明。我们先来创建抽象迭代器 IteratorIterator(这里为了和 Java 中的 Iterator 接口区别开),声明为泛型接口,接收类型参数E,同时声明四个方法:reset()、next()、currentItem() 和 hasNext()。

public interface IteratorIterator<E> {
    void reset();       //重置为第一个元素
    E next();           //获取下一个元素
    E currentItem();    //检索当前元素
    boolean hasNext();  //判断是否还有下一个元素存在.
}

接下来,我们再来定义抽象集合 ListList(同样为了和 Java 中的 List 接口区别开),也声明为泛型接口,接收类型参数 E,声明一个创建迭代器 IteratorIterator 的方法 iterator()。

public interface ListList<E> {
    IteratorIterator<E> iterator();
}

然后,我们构造一个对象 Topic,对象中只包含 name 属性以及其构造函数和 get、set 方法。

public class Topic {
    private String name;
    public Topic(String name) {
        super();
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

再接着实现一个具体的迭代器类 TopicIterator,其中包含属性为 Topic 的数组和一个记录对象存储位置的对象 position。当我们执行 next() 方法时,会获取当前记录位置的对象,至于 reset() 则会重置对象在数组中的位置为 0,currentItem() 方法则会返回当前位置下的对象,hasNext() 则判断当前位置是否越界。

public class TopicIterator implements IteratorIterator<Topic> {
    private Topic[] topics;
    private int position;
    public TopicIterator(Topic[] topics) {
        this.topics = topics;
        position = 0;
    }
    @Override
    public void reset() {
        position = 0;
    }
    @Override
    public Topic next() {
        return topics[position++];
    }
    @Override
    public Topic currentItem() {
        return topics[position];
    }
    @Override
    public boolean hasNext() {
        if(position >= topics.length) {
            return false;
        }
        return true;
    }
}

同样,还需要实现一个具体的集合类 TopicList,该类中只实现一个创建迭代器的方法,返回对应具体迭代器的类方法。

public class TopicList implements ListList<Topic>{
    private Topic[] topics;
    public TopicList(Topic[] topics)
    {
        this.topics = topics;
    }
    @Override
    public IteratorIterator<Topic> iterator() {
        return new TopicIterator(topics);
    }
}

最后,我们再来运行一段单元测试:

public class Client {
    public static void main(String[] args) {
        Topic[] topics = new Topic[5];
        topics[0] = new Topic("topic1");
        topics[1] = new Topic("topic2");
        topics[2] = new Topic("topic3");
        topics[3] = new Topic("topic4");
        topics[4] = new Topic("topic5");
        ListList<Topic> list = new TopicList(topics);
        IteratorIterator<Topic> iterator = list.iterator();
        while(iterator.hasNext()) {
            Topic currentTopic = iterator.next();
            System.out.println(currentTopic.getName());
        }
    }
}
//输出结果
topic1
topic2
topic3
topic4
topic5

上面的代码实现非常简单,如果对 Java 非常熟悉的话,就能在 JDK 的类库中找到 List 的源码实现。

正是因为迭代器模式的使用场景非常明确,所以在实际的开发中,它的应用非常广泛,几乎所有涉及集合的遍历时都会使用到迭代器模式。

三、为什么使用迭代器模式?

分析完迭代器模式的原理和使用场景后,我们再来说说使用迭代器模式的原因,可总结为以下两个。

第一个,减少程序中重复的遍历代码。我们都知道,对于放入一个集合容器中的多个对象来说,访问必然涉及遍历算法。如果我们不将遍历算法封装到容器里(比如,List、Set、Map 等),那么就需要使用容器的人自行去实现遍历算法,这样容易造成很多重复的循环和条件判断语句出现,不利于代码的复用和扩展,同时还会暴露不同容器的内部结构。而使用迭代器模式是将遍历算法作为容器对象自身的一种“属性方法”来使用,能够有效地避免写很多重复的代码,同时又不会暴露内部结构。

第二个,为了隐藏统一遍历集合的方法逻辑。迭代器模式把对不同集合类的访问逻辑抽象出来,这样在不用暴露集合内部结构的情况下,可以隐藏不同集合遍历需要使用的算法,同时还能够对外提供更为简便的访问算法接口。

四、迭代器模式的优缺点是什么?

通过上述分析,我们也可以总结出使用迭代器模式有以下优点。

  • 满足单一职责原则。由于迭代器模式是将遍历算法代码统一抽取封装为独立的类,这个类的职责便只有一个——遍历查询所有数据,所以这很符合单一职责原则。

  • 满足开闭原则。当需要对新的对象集合进行扩展时,只需要新增具体的对象迭代器和具体的集合类便能方便地进行扩展。

  • 可以并行遍历同一集合。因为每个对象都有自身的遍历器对象,那么可以同时使用这个遍历器来进行遍历,而无须等待。

  • 可以减少直接使用 for 循环的重复代码问题。直接使用 for 循环的缺点在于必须事先知道集合的数据结构,而一旦我们需要更换一种对象集合的话,则可能需要实现相同的循环逻辑。同时,代码会因此变成了一种硬编码形式,每次都需要修改代码才能进行新的结构的遍历。而使用迭代器模式则可以很好地来避免这个问题。

同样,迭代器模式也有一些缺点。

  • 增加子类数量。当新增某种集合类型的迭代器时,还得新增对应类型的迭代器和集合对象,这会增加很多不同的子类。

  • 增加系统复杂性。由于分离了更为抽象的遍历算法逻辑,所以对于那些不了解设计模式的维护者来说,相当于变相地增加新的复杂性。

迭代器模式的实现原理非常简单,关键思想是将访问和遍历的职责从集合对象中分离出来,放入标准的协议对象中。这样既能对客户端隐藏复杂结构的遍历访问方式,也能提供减少重复遍历的代码实现。

现在几乎所有编程语言都会实现迭代器模式,主要被实现为类库来使用,可以说使用非常普遍。其实,除了编程语言之外,很多组件也有应用,比如 Redis 中的 rehash() 操作,就是迭代器模式的体现,而且 Redis 更进一步地还区分了安全迭代器和非安全迭代器。

文章(专栏)将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发

个人小工具程序上线啦,通过公众号(服务端技术精选)菜单【个人工具】即可体验,欢迎大家体验后提出优化意见

标签:遍历,职责,迭代,topics,Topic,集合,public
From: https://blog.51cto.com/jiangyi/12080789

相关文章

  • 从数据中台到数据飞轮:持续迭代是关键
    在当今快速迭代的大数据时代,企业如何有效利用日益积累的海量数据,已成为业界普遍关注的话题。数据中台,作为企业集成和管理数据的中枢平台,为数据的存储和初步处理提供了便利。但光有数据中台,并不足以实现数据的最大价值。此时,“数据飞轮”概念的引入,被视为推动企业数据能力向更高层次......
  • C++容器list底层迭代器的实现逻辑~list相关函数模拟实现
    目录1.两个基本的结构体搭建2.实现push_back函数3.关于list现状的分析(对于我们如何实现这个迭代器很重要)3.1和string,vector的比较3.2对于list的分析3.3总结4.迭代器类的封装5.list容器里面其他函数的实现6.个人总结7.代码附录1.两个基本的结构体搭建首先就是我......
  • BFS 马的遍历————洛谷p1443
    马的遍历题目描述有一个\(n\timesm\)的棋盘,在某个点\((x,y)\)上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。输入格式输入只有一行四个整数,分别为\(n,m,x,y\)。输出格式一个\(n\timesm\)的矩阵,代表马到达某个点最少要走几步(不能到达则输出\(-......
  • 01背包问题之背包容量为什么要倒序遍历?(以C++代码具体实现为例)
    首先是先阐述一下背包问题:有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品只能用依次,求解将哪些物品装入背包里物品价值总和最大。这里不解释代码的其他部分,只对代码中的背包容量遍历进行具体的解释,首先给出遍历部分的代......
  • 【C++二叉树】二叉树的前序遍历、中序遍历、后序遍历递归与非递归实现
    1.二叉树的前序遍历144.二叉树的前序遍历-力扣(LeetCode) 前序遍历方式:根-左子树-右子树。递归实现:要传一个子函数来实先递归,原因是原函数返回值为vector,在原函数迭代,返回值就难处理了。非递归(迭代)实现:递归实现非常简单,非递归呢?要用迭代实现,也就是循环:还是按照根-......
  • C++ 使用范围 for 遍历多维数组用引用
    intmain(){constexprsize_trowCnt=3,colCnt=4;intia[rowCnt][colCnt];//使用for循环遍历初始赋值for(size_ti=0;i!=rowCnt;++i){for(size_tj=0;j!=colCnt;++j){ia[i][j]=i*colCnt+j......
  • 迭代器可能的报错:ConcurrentModificationException(并发修改异常)注意事项
    参考:ConcurrentModificationException(并发修改异常)可能原因和解决方法Java迭代器详解,看这一篇就够了JAVAiterator迭代器遍历一遍后不能再遍历了吗迭代器循环list集合的顶层接口Collection继承Iterable接口,实现迭代器iterator()迭代器一旦定义,不允许其他地方对其定义的集合......
  • 深入探索:深度优先遍历与广度优先遍历的奥秘与应用
    在算法和数据结构的广阔领域中,图的遍历是一个核心且基础的概念,它支撑着众多高级算法和应用的实现。深度优先遍历(DFS)和广度优先遍历(BFS)作为图的两种基本遍历方式,不仅具有深刻的理论意义,还广泛应用于各种实际问题中。本文将更深入地探讨这两种遍历方式的原理、实现细节、性能......
  • 目录遍历
    是什么?目录遍历是网络配置缺陷,导致目录可以被任意浏览,使得一些隐私文件和目录被泄露。为什么?没有对../目录设置过滤、加密或权限,恶意用户可以跳转遍历服务器上的任意文件。防御方案加密参数传递的数据对参数进行base64加密后在进行传参编码绕过URL编码绕过文件后缀过滤......
  • 【C++二叉树】105.从前序与中序遍历序列构造二叉树
    105.从前序与中序遍历序列构造二叉树-力扣(LeetCode)根据前序遍历和中序遍历构建二叉树前序遍历访问方式:根-左子树-右子树中序遍历访问方式:左子树-根-右子树思路分析:前序+中序可以构建一颗二叉树:前序遍历可以确定根,中序遍历可以确定左子树的中序区间和右子树的中序区......