迭代器模式:統一存取集合元素的標準化介面
迭代器模式提供一致的方式遍歷不同資料結構 (陣列、鏈表、樹、資料庫結果集),隱藏內部實現細節。PHP 內建 Iterator、IteratorAggregate 介面與 SPL (Standard PHP Library) 提供豐富迭代器。
核心介面定義
<?php
// PHP 內建介面 (PHP 5+)
interface Iterator extends Traversable {
public function current(): mixed; // 當前元素
public function next(): void; // 移至下一個
public function key(): mixed; // 當前鍵值
public function valid(): bool; // 是否有效
public function rewind(): void; // 重置至第一個
}
interface IteratorAggregate extends Traversable {
public function getIterator(): Traversable; // 回傳 Iterator 實例
}
?>
手工實作:圖書館藏書迭代器
<?php
// 1. 領域物件
class Book {
public function __construct(
public string $title,
public string $author,
public int $year
) {}
}
// 2. 集合類別:實作 IteratorAggregate
class Library implements IteratorAggregate {
private array $books = [];
public function addBook(Book $book): void {
$this->books[] = $book;
}
public function getIterator(): Traversable {
// 回傳內建 ArrayIterator (已實作 Iterator)
return new ArrayIterator($this->books);
}
// 進階:自訂過濾迭代器
public function getBooksAfterYear(int $year): Traversable {
return new CallbackFilterIterator(
new ArrayIterator($this->books),
fn($book) => $book->year > $year
);
}
}
// 3. 使用範例
$library = new Library();
$library->addBook(new Book("Design Patterns", "GoF", 1994));
$library->addBook(new Book("Clean Code", "Robert Martin", 2008));
$library->addBook(new Book("PHP 8 in Action", "Author", 2023));
// 基本遍歷
foreach ($library as $book) {
echo "{$book->title} ({$book->year})\n";
}
// 過濾遍歷
echo "\n2000 年後出版:\n";
foreach ($library->getBooksAfterYear(2000) as $book) {
echo "{$book->title} ({$book->year})\n";
}
?>
SPL 內建迭代器大全
| 迭代器 |
用途 |
範例 |
ArrayIterator |
陣列遍歷 |
new ArrayIterator([1,2,3]) |
DirectoryIterator |
目錄掃描 |
new DirectoryIterator('/path') |
RecursiveDirectoryIterator |
遞迴目錄掃描 |
new RecursiveDirectoryIterator('/path') |
FilesystemIterator |
檔案系統 (跳過 . ..) |
new FilesystemIterator('/path') |
GlobIterator |
Glob 模式匹配 |
new GlobIterator('*.php') |
XMLReader |
XML 流式解析 |
$r = new XMLReader(); $r->open('file.xml'); |
PDOStatement |
資料庫結果集 |
$stmt->fetchAll() 但可直接 foreach($stmt as $row) |
SplFileObject |
檔案逐行讀取 |
new SplFileObject('data.csv') |
過濾與轉換迭代器 (裝飾器模式應用)
<?php
$numbers = new ArrayIterator(range(1, 20));
// 1. 過濾:只保留偶數
$even = new CallbackFilterIterator($numbers, fn($v) => $v % 2 === 0);
// 2. 映射:平方
$squared = new CallbackFilterIterator($even, fn($v) => $v * $v); // 錯誤:Filter 不能映射
// 正確用 IteratorIterator 包裝或用 Generator
// 正確映射方式:Generator (PHP 5.5+)
function map(Traversable $src, callable $fn): Generator {
foreach ($src as $k => $v) yield $k => $fn($v);
}
$squared = map($even, fn($v) => $v * $v);
// 3. 限制:前 5 個
$limited = new LimitIterator($squared, 0, 5);
foreach ($limited as $v) {
echo "$v\n"; // 4, 16, 36, 64, 100
}
?>
自訂迭代器:資料庫分頁迭代器
<?php
class PaginatedResult implements Iterator {
private PDOStatement $stmt;
private int $pageSize;
private int $currentPage = 0;
private array $currentPageData = [];
private int $currentIndex = 0;
private bool $fetchedAll = false;
public function __construct(PDO $pdo, string $sql, int $pageSize = 100) {
$this->stmt = $pdo->prepare($sql);
$this->stmt->execute();
$this->pageSize = $pageSize;
}
public function rewind(): void {
$this->currentPage = 0;
$this->currentIndex = 0;
$this->currentPageData = [];
$this->fetchedAll = false;
$this->fetchNextPage();
}
public function current(): mixed {
return $this->currentPageData[$this->currentIndex] ?? null;
}
public function key(): mixed {
return $this->currentPage * $this->pageSize + $this->currentIndex;
}
public function next(): void {
$this->currentIndex++;
if ($this->currentIndex >= count($this->currentPageData)) {
$this->fetchNextPage();
}
}
public function valid(): bool {
return !$this->fetchedAll && $this->currentIndex < count($this->currentPageData);
}
private function fetchNextPage(): void {
$this->currentPageData = $this->stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($this->currentPageData)) {
$this->fetchedAll = true;
}
$this->currentIndex = 0;
$this->currentPage++;
}
}
// 使用:記憶體效率處理百萬筆資料
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$iterator = new PaginatedResult($pdo, "SELECT * FROM huge_table");
foreach ($iterator as $row) {
// 處理每筆資料,記憶體只佔用一頁大小
process($row);
}
?>
Generator:輕量級迭代器 (PHP 5.5+)
<?php
// 斐波那契數列 Generator
function fibonacci(int $limit): Generator {
$a = 0; $b = 1;
for ($i = 0; $i < $limit; $i++) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
foreach (fibonacci(10) as $n) {
echo "$n "; // 0 1 1 2 3 5 8 13 21 34
}
// 檔案逐行處理 (記憶體友善)
function readLines(string $filename): Generator {
$fh = fopen($filename, 'r');
while (($line = fgets($fh)) !== false) {
yield rtrim($line);
}
fclose($fh);
}
foreach (readLines('huge.log') as $line) {
if (str_contains($line, 'ERROR')) {
echo "Found: $line\n";
}
}
?>
迭代器模式優缺點總結
| 優點 |
缺點 |
| 統一遍歷介面,客戶端不依賴具體集合類別 |
簡單陣列遍歷引入額外複雜度 |
| 支援多種遍歷方式 (正向、反向、過濾、分頁) |
修改集合結構時迭代器可能失效 |
| 延遲載入、記憶體效率 (Generator、分頁迭代器) |
並發修改需額外同步機制 |
| 符合單一職責:遍歷邏輯與業務邏輯分離 |
|