![반복자](/images/patterns/cards/iterator-mini.png?id=76c28bb48f997b36965983dd2b41f02e)
PHP로 작성된 반복자
반복자는 복잡한 데이터 구조의 내부 세부 정보를 노출하지 않고 해당 구조를 차례대로 순회할 수 있도록 하는 행동 디자인 패턴입니다.
반복자 덕분에 클라이언트들은 단일 반복기 인터페이스를 사용하여 유사한 방식으로 다른 컬렉션들의 요소들을 탐색할 수 있습니다.
복잡도:
인기도:
적용 예시들: 이 패턴은 PHP에 자주 사용됩니다. 많은 프레임워크들과 라이브러리들은 이 패턴을 컬렉션을 탐색하는 표준 방법을 제공하기 위해 사용합니다.
PHP 언어에는 내장된 반복자 인터페이스가 있습니다. 이 인터페이스는 나머지 PHP 코드와 호환되는 사용자 정의된 반복자를 구축하는 데 사용할 수 있습니다.
식별법: 반복자는 그의 탐색 메서드들(예: next
, previous
등)로 쉽게 인식할 수 있습니다. 또 반복자를 사용하는 클라이언트 코드는 반복자가 순회하는 컬렉션을 직접 접근하지 못할 수도 있습니다.
개념적인 예시
이 예시는 반복자 디자인 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 PHP 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
index.php: 개념적인 예시
<?php
namespace RefactoringGuru\Iterator\Conceptual;
/**
* Concrete Iterators implement various traversal algorithms. These classes
* store the current traversal position at all times.
*/
class AlphabeticalOrderIterator implements \Iterator
{
/**
* @var WordsCollection
*/
private $collection;
/**
* @var int Stores the current traversal position. An iterator may have a
* lot of other fields for storing iteration state, especially when it is
* supposed to work with a particular kind of collection.
*/
private $position = 0;
/**
* @var bool This variable indicates the traversal direction.
*/
private $reverse = false;
public function __construct($collection, $reverse = false)
{
$this->collection = $collection;
$this->reverse = $reverse;
}
public function rewind()
{
$this->position = $this->reverse ?
count($this->collection->getItems()) - 1 : 0;
}
public function current()
{
return $this->collection->getItems()[$this->position];
}
public function key()
{
return $this->position;
}
public function next()
{
$this->position = $this->position + ($this->reverse ? -1 : 1);
}
public function valid()
{
return isset($this->collection->getItems()[$this->position]);
}
}
/**
* Concrete Collections provide one or several methods for retrieving fresh
* iterator instances, compatible with the collection class.
*/
class WordsCollection implements \IteratorAggregate
{
private $items = [];
public function getItems()
{
return $this->items;
}
public function addItem($item)
{
$this->items[] = $item;
}
public function getIterator(): Iterator
{
return new AlphabeticalOrderIterator($this);
}
public function getReverseIterator(): Iterator
{
return new AlphabeticalOrderIterator($this, true);
}
}
/**
* The client code may or may not know about the Concrete Iterator or Collection
* classes, depending on the level of indirection you want to keep in your
* program.
*/
$collection = new WordsCollection();
$collection->addItem("First");
$collection->addItem("Second");
$collection->addItem("Third");
echo "Straight traversal:\n";
foreach ($collection->getIterator() as $item) {
echo $item . "\n";
}
echo "\n";
echo "Reverse traversal:\n";
foreach ($collection->getReverseIterator() as $item) {
echo $item . "\n";
}
Output.txt: 실행 결과
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
구상적인 실제 예시
PHP에는 이미 foreach 루프와의 편리한 통합을 제공하는 내장된 반복자 인터페이스가 있기 때문에 상상할 수 있는 거의 모든 종류의 데이터 구조를 순회하기 위한 자체 반복자를 쉽게 만들 수 있습니다.
이 반복자 패턴의 예시에서는 사용자가 CSV 파일에 쉽게 접근할 수 있도록 합니다.
index.php: 개념적인 예시
<?php
namespace RefactoringGuru\Iterator\RealWorld;
/**
* CSV File Iterator.
*
* @author Josh Lockhart
*/
class CsvIterator implements \Iterator
{
const ROW_SIZE = 4096;
/**
* The pointer to the CSV file.
*
* @var resource
*/
protected $filePointer = null;
/**
* The current element, which is returned on each iteration.
*
* @var array
*/
protected $currentElement = null;
/**
* The row counter.
*
* @var int
*/
protected $rowCounter = null;
/**
* The delimiter for the CSV file.
*
* @var string
*/
protected $delimiter = null;
/**
* The constructor tries to open the CSV file. It throws an exception on
* failure.
*
* @param string $file The CSV file.
* @param string $delimiter The delimiter.
*
* @throws \Exception
*/
public function __construct($file, $delimiter = ',')
{
try {
$this->filePointer = fopen($file, 'rb');
$this->delimiter = $delimiter;
} catch (\Exception $e) {
throw new \Exception('The file "' . $file . '" cannot be read.');
}
}
/**
* This method resets the file pointer.
*/
public function rewind(): void
{
$this->rowCounter = 0;
rewind($this->filePointer);
}
/**
* This method returns the current CSV row as a 2-dimensional array.
*
* @return array The current CSV row as a 2-dimensional array.
*/
public function current(): array
{
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
$this->rowCounter++;
return $this->currentElement;
}
/**
* This method returns the current row number.
*
* @return int The current row number.
*/
public function key(): int
{
return $this->rowCounter;
}
/**
* This method checks if the end of file has been reached.
*
* @return bool Returns true on EOF reached, false otherwise.
*/
public function next(): bool
{
if (is_resource($this->filePointer)) {
return !feof($this->filePointer);
}
return false;
}
/**
* This method checks if the next row is a valid row.
*
* @return bool If the next row is a valid row.
*/
public function valid(): bool
{
if (!$this->next()) {
if (is_resource($this->filePointer)) {
fclose($this->filePointer);
}
return false;
}
return true;
}
}
/**
* The client code.
*/
$csv = new CsvIterator(__DIR__ . '/cats.csv');
foreach ($csv as $key => $row) {
print_r($row);
}
Output.txt: 실행 결과
Array
(
[0] => Name
[1] => Age
[2] => Owner
[3] => Breed
[4] => Image
[5] => Color
[6] => Texture
[7] => Fur
[8] => Size
)
Array
(
[0] => Steve
[1] => 3
[2] => Alexander Shvets
[3] => Bengal
[4] => /cats/bengal.jpg
[5] => Brown
[6] => Stripes
[7] => Short
[8] => Medium
)
Array
(
[0] => Siri
[1] => 2
[2] => Alexander Shvets
[3] => Domestic short-haired
[4] => /cats/domestic-sh.jpg
[5] => Black
[6] => Solid
[7] => Medium
[8] => Medium
)
Array
(
[0] => Fluffy
[1] => 5
[2] => John Smith
[3] => Maine Coon
[4] => /cats/Maine-Coon.jpg
[5] => Gray
[6] => Stripes
[7] => Long
[8] => Large
)