迭代器模式

目的

使一个对象可迭代,并使它看起来像一个对象的集合。

使用场景

  • 使用迭代器对一个文件的所有行(被迭代对象)进行逐行处理

说明

PHP 标准库 SPL 提供的 Iterator 接口是处理这类场景的最优方式。通常,类也会同时实现 Countable 接口,以允许在迭代对象上调用 count($object) 方法。

UML 类图

迭代器模式类图

代码

Book.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Iterator;

class Book
{
private string $author;
private string $title;

public function __construct(string $title, string $author)
{
$this->author = $author;
$this->title = $title;
}

public function getAuthor(): string
{
return $this->author;
}

public function getTitle(): string
{
return $this->title;
}

public function getAuthorAndTitle(): string
{
return $this->getTitle().' by '.$this->getAuthor();
}
}

BookList.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Iterator;

use Countable;
use Iterator;

class BookList implements Countable, Iterator
{
/**
* @var Book[]
*/
private array $books = [];
private int $currentIndex = 0;

public function addBook(Book $book)
{
$this->books[] = $book;
}

public function removeBook(Book $bookToRemove)
{
foreach ($this->books as $key => $book) {
if ($book->getAuthorAndTitle() === $bookToRemove->getAuthorAndTitle()) {
unset($this->books[$key]);
}
}

$this->books = array_values($this->books);
}

public function count(): int
{
return count($this->books);
}

public function current(): Book
{
return $this->books[$this->currentIndex];
}

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

public function next()
{
$this->currentIndex++;
}

public function rewind()
{
$this->currentIndex = 0;
}

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

测试

Tests/IteratorTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Iterator\Tests;

use DesignPatterns\Behavioral\Iterator\Book;
use DesignPatterns\Behavioral\Iterator\BookList;
use PHPUnit\Framework\TestCase;

class IteratorTest extends TestCase
{
public function testCanIterateOverBookList()
{
$bookList = new BookList();
$bookList->addBook(new Book('Learning PHP Design Patterns', 'William Sanders'));
$bookList->addBook(new Book('Professional Php Design Patterns', 'Aaron Saray'));
$bookList->addBook(new Book('Clean Code', 'Robert C. Martin'));

$books = [];

foreach ($bookList as $book) {
$books[] = $book->getAuthorAndTitle();
}

$this->assertSame(
[
'Learning PHP Design Patterns by William Sanders',
'Professional Php Design Patterns by Aaron Saray',
'Clean Code by Robert C. Martin',
],
$books
);
}

public function testCanIterateOverBookListAfterRemovingBook()
{
$book = new Book('Clean Code', 'Robert C. Martin');
$book2 = new Book('Professional Php Design Patterns', 'Aaron Saray');

$bookList = new BookList();
$bookList->addBook($book);
$bookList->addBook($book2);
$bookList->removeBook($book);

$books = [];
foreach ($bookList as $book) {
$books[] = $book->getAuthorAndTitle();
}

$this->assertSame(
['Professional Php Design Patterns by Aaron Saray'],
$books
);
}

public function testCanAddBookToList()
{
$book = new Book('Clean Code', 'Robert C. Martin');

$bookList = new BookList();
$bookList->addBook($book);

$this->assertCount(1, $bookList);
}

public function testCanRemoveBookFromList()
{
$book = new Book('Clean Code', 'Robert C. Martin');

$bookList = new BookList();
$bookList->addBook($book);
$bookList->removeBook($book);

$this->assertCount(0, $bookList);
}
}