Skip to content

迭代器模式(Iterator)

前言

迭代器模式提供一种方法顺序访问聚合对象中的元素,而不暴露其内部表示。它是现代编程语言中最普及的设计模式之一,for...ofArray.forEach 等都是迭代器的应用。本文将详细讲解迭代器模式的核心原理及实际应用。


一、核心概念

1.1 定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其对象的内部表示。

1.2 核心角色

角色说明
迭代器接口(Iterator)定义遍历元素的方法
具体迭代器(ConcreteIterator)实现遍历逻辑
聚合接口(Aggregate)定义创建迭代器的方法
具体聚合(ConcreteAggregate)实现创建迭代器

1.3 迭代器类型

类型说明
内部迭代器迭代器自己控制遍历(如 forEach)
外部迭代器客户端控制遍历(如 next())
生成器迭代器使用 yield 生成的迭代器

二、代码实现

2.1 基础实现

php
<?php
// PHP 内置 Iterator 接口(PHP 自带,无需自定义)
// interface Iterator extends Traversable {
//     public function current(): mixed;
//     public function key(): mixed;
//     public function next(): void;
//     public function rewind(): void;
//     public function valid(): bool;
// }

// 聚合接口
interface Aggregate
{
    public function createIterator(): Iterator;
}

// 具体迭代器:实现 PHP 内置 Iterator 接口
class ConcreteIterator implements Iterator
{
    private array $collection;
    private int $index = 0;

    public function __construct(array $collection)
    {
        $this->collection = array_values($collection);
    }

    public function current(): mixed
    {
        return $this->collection[$this->index] ?? null;
    }

    public function next(): void
    {
        $this->index++;
    }

    public function key(): mixed
    {
        return $this->index;
    }

    public function valid(): bool
    {
        return isset($this->collection[$this->index]);
    }

    public function rewind(): void
    {
        $this->index = 0;
    }
}

// 具体聚合
class ConcreteCollection implements Aggregate
{
    /** @var mixed[] */
    private array $items = [];

    public function addItem($item): void
    {
        $this->items[] = $item;
    }

    public function createIterator(): Iterator
    {
        return new ConcreteIterator($this->items);
    }
}

// 使用
$collection = new ConcreteCollection();
$collection->addItem("Item 1");
$collection->addItem("Item 2");
$collection->addItem("Item 3");

$iterator = $collection->createIterator();
foreach ($iterator as $item) {
    echo $item . PHP_EOL;
}

2.2 实际案例:自定义树遍历

php
<?php
// 树节点
class TreeNode
{
    public string $value;
    /** @var TreeNode[] */
    public array $children = [];

    public function __construct(string $value)
    {
        $this->value = $value;
    }

    public function add(TreeNode $child): self
    {
        $this->children[] = $child;
        return $this;
    }
}

// 深度优先迭代器
class DFSIterator implements Iterator
{
    /** @var TreeNode[] */
    private array $stack;
    private int $index = 0;
    /** @var TreeNode[] */
    private array $result;

    public function __construct(TreeNode $root)
    {
        $this->stack = [$root];
        $this->result = [];
        // 一次性生成快照结果
        while (!empty($this->stack)) {
            $node = array_pop($this->stack);
            $this->result[] = $node;
            // 逆序压入子节点(保证从左到右遍历)
            for ($i = count($node->children) - 1; $i >= 0; $i--) {
                $this->stack[] = $node->children[$i];
            }
        }
    }

    public function current(): mixed
    {
        return $this->result[$this->index] ?? null;
    }

    public function next(): void
    {
        $this->index++;
    }

    public function key(): mixed
    {
        return $this->index;
    }

    public function valid(): bool
    {
        return isset($this->result[$this->index]);
    }

    public function rewind(): void
    {
        $this->index = 0;
    }
}

// 广度优先迭代器
class BFSIterator implements Iterator
{
    /** @var TreeNode[] */
    private array $result;
    private int $index = 0;

    public function __construct(TreeNode $root)
    {
        $queue = [$root];
        $this->result = [];
        while (!empty($queue)) {
            $node = array_shift($queue);
            $this->result[] = $node;
            foreach ($node->children as $child) {
                $queue[] = $child;
            }
        }
    }

    public function current(): mixed
    {
        return $this->result[$this->index] ?? null;
    }

    public function next(): void
    {
        $this->index++;
    }

    public function key(): mixed
    {
        return $this->index;
    }

    public function valid(): bool
    {
        return isset($this->result[$this->index]);
    }

    public function rewind(): void
    {
        $this->index = 0;
    }
}

// 使用
$tree = (new TreeNode("Root"))
    ->add((new TreeNode("A"))->add(new TreeNode("A1"))->add(new TreeNode("A2")))
    ->add((new TreeNode("B"))->add(new TreeNode("B1")));

echo "=== DFS ===" . PHP_EOL;
foreach (new DFSIterator($tree) as $node) {
    echo $node->value . PHP_EOL;
}
// Root → A → A1 → A2 → B → B1

echo "=== BFS ===" . PHP_EOL;
foreach (new BFSIterator($tree) as $node) {
    echo $node->value . PHP_EOL;
}
// Root → A → B → A1 → A2 → B1

2.3 使用 PHP Generator

php
<?php
// 使用 PHP Generator 实现迭代器(PHP 5.5+)
class NumberRange
{
    public function __construct(
        private int $start,
        private int $end
    ) {}

    // 生成器:正向遍历
    public function forward(): Generator
    {
        for ($i = $this->start; $i <= $this->end; $i++) {
            yield $i;
        }
    }

    // 生成器:反向遍历
    public function backward(): Generator
    {
        for ($i = $this->end; $i >= $this->start; $i--) {
            yield $i;
        }
    }

    // 生成器:步长遍历
    public function step(int $step): Generator
    {
        for ($i = $this->start; $i <= $this->end; $i += $step) {
            yield $i;
        }
    }
}

// 使用 foreach 遍历
$range = new NumberRange(1, 10);

echo "Forward:" . PHP_EOL;
foreach ($range->forward() as $num) {
    echo $num . PHP_EOL;
}

echo "Step by 2:" . PHP_EOL;
foreach ($range->step(2) as $num) {
    echo $num . PHP_EOL;
}

三、适用场景

场景说明
集合遍历数组、链表、树等
统一遍历接口不同数据结构统一遍历
多种遍历方式DFS、BFS、正序、倒序
懒加载按需生成元素
无限序列斐波那契数列等
分页数据逐页加载

四、优缺点分析

优点缺点
统一遍历接口需要额外的迭代器类
简化集合操作增加代码复杂度
支持多种遍历简单集合过度设计
不暴露内部结构遍历时修改集合可能出错
支持懒加载需要管理迭代状态

五、常见踩坑与问题排查

5.1 遍历时修改集合

php
<?php
// 问题:遍历时修改集合导致错误
$list = [1, 2, 3, 4, 5];
foreach ($list as $item) {
    if ($item === 3) {
        $index = array_search($item, $list);
        if ($index !== false) array_splice($list, $index, 1); // 危险!
    }
}

// 解决:先收集要删除的元素,遍历后再删除
$toRemove = [];
foreach ($list as $item) {
    if ($item === 3) $toRemove[] = $item;
}
foreach ($toRemove as $item) {
    $index = array_search($item, $list);
    if ($index !== false) array_splice($list, $index, 1);
}

5.2 迭代器失效

php
<?php
// 问题:集合修改后,旧迭代器状态可能失效
$collection = new ConcreteCollection();
$collection->addItem(1);
$collection->addItem(2);
$iter = $collection->createIterator();
$collection->addItem(3); // 修改后迭代器可能不包含新元素

// 解决:使用快照迭代器
class SnapshotIterator implements Iterator
{
    private array $snapshot;
    private int $index = 0;

    public function __construct(array $collection)
    {
        $this->snapshot = array_values($collection); // 快照
    }

    public function current(): mixed
    {
        return $this->snapshot[$this->index] ?? null;
    }

    public function next(): void
    {
        $this->index++;
    }

    public function key(): mixed
    {
        return $this->index;
    }

    public function valid(): bool
    {
        return isset($this->snapshot[$this->index]);
    }

    public function rewind(): void
    {
        $this->index = 0;
    }
}

六、优化方案与进阶

6.1 PHP IteratorAggregate 接口

php
<?php
// 实现 IteratorAggregate 使对象可被 foreach 遍历
class Range implements IteratorAggregate
{
    public function __construct(
        private int $start,
        private int $end
    ) {}

    public function getIterator(): Iterator
    {
        $current = $this->start;
        $end = $this->end;

        return new class($current, $end) implements Iterator {
            private int $current;
            private int $end;
            private int $key = 0;

            public function __construct(int $start, int $end)
            {
                $this->current = $start;
                $this->end = $end;
            }

            public function current(): mixed
            {
                return $this->current;
            }

            public function next(): void
            {
                $this->current++;
                $this->key++;
            }

            public function key(): mixed
            {
                return $this->key;
            }

            public function valid(): bool
            {
                return $this->current <= $this->end;
            }

            public function rewind(): void
            {
                $this->current = $this->start ?? $this->end;
                $this->key = 0;
            }
        };
    }
}

// 使用
$range = new Range(1, 5);
foreach ($range as $num) {
    echo $num . PHP_EOL;
}

6.2 无限序列

php
<?php
// 斐波那契数列(无限序列)
function fibonacci(): Generator
{
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

// 使用:取前 10 个
$fib = fibonacci();
for ($i = 0; $i < 10; $i++) {
    echo $fib->current() . PHP_EOL;
    $fib->next();
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

七、全文总结

迭代器模式的核心是 顺序访问聚合元素,不暴露内部结构

核心要点

  1. 迭代器将遍历逻辑从集合中分离
  2. 支持多种遍历方式(DFS、BFS、正序、倒序)
  3. ES6 Generator 简化了迭代器实现
  4. 实现 Symbol.iterator 可使对象支持 for...of
  5. 注意遍历时修改集合的问题,可使用快照解决