迭代器设计模式

迭代器设计模式是一种行为模式,它提供了一种标准方法来按顺序访问集合的元素,而无需公开其内部结构。

它在以下情况下特别有用:

您需要 以一致且灵活的方式遍历集合(如列表、树或图形)。

您希望支持多种迭代方式(例如,向前、向后、过滤或跳过元素)。

您希望将遍历逻辑与集合结构分离,以便客户端不依赖于内部表示。

当面临这种需求时,开发人员通常会编写自定义 循环或直接公开底层数据结构(如 或 )。例如,类 可能会公开其 数组,并让客户端根据需要进行迭代。forArrayListLinkedListPlaylistsongs

但这种方法使客户端与集合的内部结构紧密耦合,并且违反了封装。如果内部存储发生更改,客户端代码将中断。添加新的遍历逻辑或支持延迟迭代也变得困难。

迭代器模式通过将迭代逻辑抽象到一个专用对象(迭代器)中来解决这个问题。集合通过类似 的方法提供迭代器,客户端使用它来逐个访问元素。 createIterator()

这使得更改集合的结构、添加新的遍历样式以及保留干净、封装的设计变得容易。

让我们通过一个真实世界的示例来了解如何应用迭代器模式来构建一种更易于维护、可扩展和标准化的方法来遍历集合。

问题:遍历播放列表

想象一下,您正在构建一个允许用户创建和管理播放列表的音乐流媒体应用程序。每个播放列表都存储一个歌曲列表并提供以下功能:

一首一首地播放歌曲

跳到下一首或上一首歌曲

随机播放歌曲

显示当前歌曲队列

假设您从一个简单的实现开始:

复制代码
import java.util.List;
import java.util.ArrayList;

public class Playlist implements IterableCollection<String> {
    private final List<String> songs = new ArrayList<>();

    public void addSong(String song) {
        songs.add(song);
    }

    public String getSongAt(int index) {
        return songs.get(index);
    }

    public int getSize() {
        return songs.size();
    }

    @Override
    public Iterator<String> createIterator() {
        return new PlaylistIterator(this);
    }
}

客户端代码可能如下所示:

复制代码
public class MusicPlayer {
    public static void main(String[] args) {
        Playlist playlist = new Playlist();
        playlist.addSong("Shape of You");
        playlist.addSong("Bohemian Rhapsody");
        playlist.addSong("Blinding Lights");

        Iterator<String> iterator = playlist.createIterator();

        System.out.println("Now Playing:");
        while (iterator.hasNext()) {
            System.out.println("🎵 " + iterator.next());
        }
    }
}

为什么这是一个问题

    1. 破坏封装
      通过公开歌曲的内部列表 (),您可以允许客户端直接修改集合。这可能会导致意想不到的副作用,例如从列表中删除歌曲、重新排序歌曲或注入空值。getSongs()
    1. 将客户端与集合类型紧密耦合
      客户端假定播放列表使用 .如果您决定更改内部存储(例如,更改为 、 数组,甚至是流式缓冲区),则客户端代码会中断。ListLinkedList
    1. 有限的遍历选项
      客户端停留在默认的迭代顺序上。支持多种遍历样式(例如,反向、随机、过滤)每次都需要重写循环逻辑------这违反了单一责任和开放/关闭原则。
    1. 重复和刚性
      随着更多功能的添加(例如,预览歌曲、仅播放收藏夹),遍历歌曲的逻辑会在多个类之间重复。

我们真正需要什么

我们需要一种干净、标准化的方式来:

遍历播放列表中的歌曲而不公开内部收藏

允许不同的遍历样式(前进、后退、随机播放)

从数据结构本身抽象出迭代逻辑

保留封装并允许播放列表在内部更改,而不会影响客户端代码

这正是迭代器模式的用武之地。

迭代器模式

迭代器模式提供了一种标准方法来遍历集合中的元素,而无需暴露其内部结构。

该集合不是让客户端访问底层数据(如 ),而是提供了一个迭代器对象,该对象通过公共接口(例如 , )提供对其元素的顺序访问 。ArrayListhasNext()next()

类图

    1. 迭代器(接口)
      定义遍历集合的协定。
      声明遍历元素的方法,例如:
      hasNext()--- 检查是否有更多元素
      next()--- 返回序列中的下一个元素
    1. ConcreteIterator
      实现 特定集合的接口。 Iterator
      维护集合中的当前位置,并一次循环访问一个项目。
    1. IterableCollection(接口)
      定义创建迭代器的方法。
      将集合的结构与遍历逻辑分开
    1. 混凝土收藏
      存储实际数据并实现 接口。IterableCollection
      它将遍历逻辑委托给迭代器。
      返回迭代器以遍历集合项

实现迭代器

现在让我们实现迭代器模式,将我们在播放列表中遍历歌曲的方式与播放列表的内部结构方式解耦。

我们将构建:

存储歌曲的 A Playlist

可以在 播放列表中移动的PlaylistIterator

使用迭代器的客户端,无需知道播放列表如何存储其歌曲

    1. 定义 接口Iterator
      此接口定义循环访问集合所需的标准作。

    public interface Iterator<T> {
    boolean hasNext();
    T next();
    }

    1. 定义 接口IterableCollection
      这表示可以生成迭代器的任何集合。

    public interface IterableCollection<T> {
    Iterator<T> createIterator();
    }

    1. 实施具体收集 --Playlist
      该类将保存歌曲列表并返回一个迭代器来遍历它们。

    import java.util.List;
    import java.util.ArrayList;

    public class Playlist implements IterableCollection<String> {
    private final List<String> songs = new ArrayList<>();

    复制代码
     public void addSong(String song) {
         songs.add(song);
     }
    
     public String getSongAt(int index) {
         return songs.get(index);
     }
    
     public int getSize() {
         return songs.size();
     }
    
     @Override
     public Iterator<String> createIterator() {
         return new PlaylistIterator(this);
     }

    }

    1. 实现具体迭代器 --PlaylistIterator
      本课程知道如何 一次遍历一首歌曲。Playlist

    public class PlaylistIterator implements Iterator<String> {
    private final Playlist playlist;
    private int index = 0;

    复制代码
     public PlaylistIterator(Playlist playlist) {
         this.playlist = playlist;
     }
    
     @Override
     public boolean hasNext() {
         return index < playlist.getSize();
     }
    
     @Override
     public String next() {
         return playlist.getSongAt(index++);
     }

    }

    1. 客户端代码 -- 使用迭代器
      客户端现在可以循环访问播放列表,而无需知道它在内部是如何实现的。

    public class MusicPlayer {
    public static void main(String[] args) {
    Playlist playlist = new Playlist();
    playlist.addSong("Shape of You");
    playlist.addSong("Bohemian Rhapsody");
    playlist.addSong("Blinding Lights");

    复制代码
         Iterator<String> iterator = playlist.createIterator();
    
         System.out.println("Now Playing:");
         while (iterator.hasNext()) {
             System.out.println("🎵 " + iterator.next());
         }
     }

    }

输出

复制代码
Now Playing:
🎵 Shape of You
🎵 Bohemian Rhapsody
🎵 Blinding Lights

我们取得了什么成就

封装:客户端从不直接访问内部列表

一致的遍历接口:所有迭代器都遵循相同的 / 模式hasNext()next()

开放/关闭原则:我们可以在不更改播放列表或播放器的情况下添加新的迭代器(反向、随机播放)

灵活的架构:适用于不同的集合类型,而不仅仅是列表

可重用逻辑:迭代器可以在任何需要遍历播放列表的上下文中使用

其他阅读材料:

复制代码
https://pan.baidu.com/s/1c1oQItiA7nZxz8Rnl3STpw?pwd=yftc
https://pan.quark.cn/s/dec9e4868381