Iterator
概要
走査と操作を分ける 一つの走査に対して 複数の操作を行えたり
マンガでわかる Iterator
マンガでわかる Iterator #デザインパターン - Qiita
でざぱたんで覚える Iterator
ちびキャラは「イテレータたん」。荷物の中身も使い道も気にしない黒猫の運び屋。「順に届ける」ことだけを請け負うから、どんな荷(操作)とも組める——ループの添字を隠すことよりも、走査(順に辿る)と操作(要素に何をするか)の分離こそがIteratorの真価、と本書は強調する。一つの走査に複数の操作を、複数の走査に一つの操作を自由に組み合わせられる。
出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.158〜)
意図
Iterableな構造はたくさんある。IOとかMAPとかリンクリストとか。それらに対して
for($i = 0; $i < $length; $i ++ ){
...
}
必ずしもこのように走査できるわけではない。
そこで「関心の分離」。反復可能という概念だけ抽出して、ロジックは個々のデータ構造にまかせる。
クラス図
このサイトの実装(本棚の走査)での対応関係:
classDiagram
class Aggregate {
<<interface>>
+iterator() Iterator
}
class Iterator {
<<interface>>
+hasNext() bool
+next() Object
}
class BookShelf {
-books Book[]
-last int
+getBookAt(index) Book
+appendBook(book)
+getLength() int
+iterator() Iterator
}
class BookShelfIterator {
-bookShelf BookShelf
-index int
+hasNext() bool
+next() Object
}
class Book {
-name String
+getName() String
}
Aggregate <|.. BookShelf
Iterator <|.. BookShelfIterator
BookShelf o-- Book
BookShelf ..> BookShelfIterator : creates
BookShelfIterator --> BookShelf : bookShelf
留意点
- モダン言語なら標準で搭載されていることが多い機能なので、自分で書くことを目的にする必要はない。
- あくまで名前を持つ概念の意味を理解して共有することが目的。
- それはそれとしてIteratorは偉大なのでよく味わうべき
やり方
イテレータには以下を実装する
- 最初の要素に戻す
- 現在位置が有効かどうか
- 現在の要素を返す
- 次の要素に進める
- 現在のキーを返す
Java
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
} else {
return false;
}
}
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
public interface Aggregate {
public abstract Iterator iterator();
}
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println(book.getName());
}
}
}
Go
Aggregate/Iteratorともにinterfaceで表現。Java版のIterator#next()はGenerics以前の Javaの制約でObjectを返し呼び出し側でキャストするが、Goには型消去された共通の親が無いため Next() anyにして呼び出し側で型アサーションする形にした(Go 1.23以降のiter.Seq (range-over-func)という言語標準の反復子機構はあえて使わず、hasNext()/next()という Java版そのままの構造で移植している)。
実行: go run ./GoF/patterns/Iterator/go
package main
// 実行: go run ./GoF/patterns/Iterator/go
// Aggregate はJava版 Aggregate.java 相当。
// 「自分用のIteratorを作れる」ことだけを約束するインタフェース。
//
// Go の interface は暗黙実装(implements を書かない)。BookShelf がこれを満たす。
type Aggregate interface {
Iterator() Iterator
}
package main
// 実行: go run ./GoF/patterns/Iterator/go
// Iterator はJava版 Iterator.java 相当。BookShelfIterator がこれを実装するConcreteIterator。
//
// Java版はGenerics以前のJavaの制約でnext()がObjectを返し、呼び出し側でキャストする
// (Main.javaの (Book)it.next())。Goには型消去されたObject相当の共通の親が無いので、
// Java版に一番忠実に対応させるならnext()の戻り値をany(interface{})にして呼び出し側で
// 型アサーションする、という形になる。
//
// Go 1.23以降にはrange-over-func(iter.Seq)という言語標準の反復子機構があるが、
// ここではあえて使わない。比較サイトの趣旨はJava版の構造をそのまま多言語に映すこと
// なので、hasNext()/next()という明示的なインタフェースをそのままGoのinterfaceとして
// 移植する。
type Iterator interface {
HasNext() bool
Next() any
}
package main
// 実行: go run ./GoF/patterns/Iterator/go
// Book はJava版 Book.java 相当。
type Book struct {
name string
}
// NewBook はコンストラクタ相当。
func NewBook(name string) *Book {
return &Book{name: name}
}
// Name は本の名前を得る。
func (b *Book) Name() string {
return b.name
}
package main
// 実行: go run ./GoF/patterns/Iterator/go
// BookShelf はJava版 BookShelf.java 相当。AggregateのConcreteAggregate。
//
// Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、
// Goではsliceで足りるので、Composite/goのDirectoryと同様に可変長で持つ
// (maxsizeはappendの容量ヒントとしてだけ使う)。
type BookShelf struct {
books []*Book
}
// NewBookShelf はコンストラクタ相当。
func NewBookShelf(maxsize int) *BookShelf {
return &BookShelf{books: make([]*Book, 0, maxsize)}
}
// BookAt は指定indexの本を得る。
func (s *BookShelf) BookAt(index int) *Book {
return s.books[index]
}
// AppendBook は本棚の末尾に本を追加する。
func (s *BookShelf) AppendBook(book *Book) {
s.books = append(s.books, book)
}
// Length は本棚にある本の冊数を返す。
func (s *BookShelf) Length() int {
return len(s.books)
}
// Iterator はJava版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。
func (s *BookShelf) Iterator() Iterator {
return NewBookShelfIterator(s)
}
package main
// 実行: go run ./GoF/patterns/Iterator/go
// BookShelfIterator はJava版 BookShelfIterator.java 相当。IteratorのConcreteIterator。
type BookShelfIterator struct {
bookShelf *BookShelf
index int
}
// NewBookShelfIterator はコンストラクタ相当。
func NewBookShelfIterator(bookShelf *BookShelf) *BookShelfIterator {
return &BookShelfIterator{bookShelf: bookShelf, index: 0}
}
// HasNext は次の要素があるとき true。
func (it *BookShelfIterator) HasNext() bool {
return it.index < it.bookShelf.Length()
}
// Next は現在位置の本を返し、内部indexを1つ進める。
func (it *BookShelfIterator) Next() any {
book := it.bookShelf.BookAt(it.index)
it.index++
return book
}
package main
import "fmt"
// 実行: go run ./GoF/patterns/Iterator/go
//
// Java版Main.javaと同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。
func main() {
bookShelf := NewBookShelf(4)
bookShelf.AppendBook(NewBook("Around the World in 80 Days"))
bookShelf.AppendBook(NewBook("Bible"))
bookShelf.AppendBook(NewBook("Cinderella"))
bookShelf.AppendBook(NewBook("Daddy-Long-Legs"))
it := bookShelf.Iterator()
for it.HasNext() {
// Java版の (Book)it.next() と同じ、呼び出し側での型アサーション(ダウンキャスト)。
book := it.Next().(*Book)
fmt.Println(book.Name())
}
}
PHP
<?php
ini_set("display_errors", "1");
/**
* 走査と操作を分ける
* 一つの走査に対して
* 複数の操作を行えたり
*/
/**
* 本
*/
class Book
{
private $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
/**
* 自前でイテレータのインスタンスを用意
* イテレータに必要なのは
* 1. 最初の要素に戻す
* 2. 現在位置が有効かどうか
* 3. 現在の要素を返す
* 4. 次の要素に進める
* 5. 現在のキーを返す
*/
interface Iteratable
{
public function rewind();
public function valid();
public function current();
public function next();
public function key();
}
/**
* 本棚
*/
class BookShelf
{
private $bookShelfIterator;
public function __construct($bookShelfIterator)
{
$this->bookShelfIterator = $bookShelfIterator;
}
/**
* 本棚の本をforで数え上げ
* 一冊ずつfunctionの引数にして実行
*
* @param function $function
*/
public function run($function)
{
for (
$this->bookShelfIterator->rewind();
$current = $this->bookShelfIterator->current();
$current = $this->bookShelfIterator->next()
) {
$function($current);
}
}
}
/**
* 本棚のイテレータ
*
*/
class BookShelfIterator implements Iteratable
{
private $bookList = array();
private $index = 0;
public function __construct($bookShelf)
{
$this->bookList = $bookShelf;
}
/**
* 現在位置より抱えているほんの数の方が大きいときtrue
*/
public function valid()
{
return count($this->bookList) > $this->index;
}
/***
* 初期位置に戻す
*/
public function rewind()
{
$this->index = 0;
}
/**
* 現在位置の本を返す
* @return Book
*/
public function current()
{
return $this->bookList[$this->index];
}
/**
* 要素を次に進める
* @return int
*/
public function next()
{
return $this->index++;
}
/**
* 現在のキーを返す
* @return int
*/
public function key()
{
return $this->index;
}
}
print "自前イテレータ <br>";
$bookShelf = new BookShelf(new BookShelfIterator(
array(
new Book("本1"),
new Book("本2"),
new Book("本3"),
)
));
// PHPって無名関数あったのか...
$bookShelf->run(
function ($book) {
print $book->getName() . "<br>";
}
);
class BookShelfIterator2 extends BookShelfIterator implements Iterator
{
}
print "標準イテレータ <br>";
$bookShelfIterator2 = new BookShelfIterator2(array(
new Book("本1"),
new Book("本2"),
new Book("本3"),
));
// iteratorを実装しているので
// foreachできる
// arrayをiteratorでwrapしている
foreach ($bookShelfIterator2 as $key => $book) {
print $book->getName() . "<br>";
}
TypeScript
Aggregate<T>/Iterator<T>というJava版そのままのインタフェース構造に、TypeScriptの Genericsで型を付けた(Java版のObjectキャストが不要になる)。Iterable<T>/ Symbol.iteratorというfor-ofと統合された言語標準の反復子機構はあえて使っていない。
実行: npx tsx GoF/patterns/Iterator/typescript/main.ts
// Iterator パターン: 集合体層 (Java版の Aggregate.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { Iterator } from "./iterator";
// Aggregate: 「自分用のIteratorを作れる」ことだけを約束するインタフェース。
export interface Aggregate<T> {
iterator(): Iterator<T>;
}
// Iterator パターン: イテレータ層 (Java版の Iterator.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版のIterator#next()はGenerics以前のJavaの制約でObjectを返し、呼び出し側で
// キャストしている(Main.javaの (Book)it.next())。TypeScriptにはGenericsがあるので、
// ここではIterator<T>としてBookをそのまま返せるようにし、呼び出し側のキャストを
// 不要にした。
//
// TypeScriptにはIterable<T>/Symbol.iteratorというfor-ofと統合された言語標準の反復子
// 機構もあるが、あえて使わない。比較サイトの趣旨はJava版の構造をそのまま多言語に映す
// ことなので、hasNext()/next()という明示的なメソッドを持つインタフェースとして
// Java版をそのまま移植する。
export interface Iterator<T> {
hasNext(): boolean;
next(): T;
}
// Book層 (Java版の Book.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
export class Book {
constructor(private readonly name: string) {}
getName(): string {
return this.name;
}
}
// BookShelf層 (Java版の BookShelf.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、TypeScriptでは
// 配列が可変長なので、Composite版のDirectoryと同様にmaxsize引数を外している。
import { Aggregate } from "./aggregate";
import { Book } from "./book";
import { BookShelfIterator } from "./book_shelf_iterator";
import { Iterator } from "./iterator";
// BookShelf: AggregateのConcreteAggregate。
export class BookShelf implements Aggregate<Book> {
private readonly books: Book[] = [];
getBookAt(index: number): Book {
return this.books[index];
}
appendBook(book: Book): void {
this.books.push(book);
}
getLength(): number {
return this.books.length;
}
// Java版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。
iterator(): Iterator<Book> {
return new BookShelfIterator(this);
}
}
// BookShelfIterator層 (Java版の BookShelfIterator.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
import { Book } from "./book";
import { BookShelf } from "./book_shelf";
import { Iterator } from "./iterator";
// BookShelfIterator: IteratorのConcreteIterator。
export class BookShelfIterator implements Iterator<Book> {
private index = 0;
constructor(private readonly bookShelf: BookShelf) {}
hasNext(): boolean {
return this.index < this.bookShelf.getLength();
}
next(): Book {
const book = this.bookShelf.getBookAt(this.index);
this.index++;
return book;
}
}
// Iterator パターン: 本棚から本を順番に取り出す (Java版Main.javaと同じお題)
//
// 実行: npx tsx GoF/patterns/Iterator/typescript/main.ts
//
// Java版と同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。
import { Book } from "./book";
import { BookShelf } from "./book_shelf";
function main(): void {
const bookShelf = new BookShelf();
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
const it = bookShelf.iterator();
while (it.hasNext()) {
const book = it.next();
console.log(book.getName());
}
}
main();
Python
Aggregate/IteratorともにABCで表現し、Java版のhasNext()/next()を has_next()/next()としてそのまま移植した。Pythonには__iter__/__next__ プロトコル(for文と統合された言語標準の反復子機構)があるが、Java版の構造をそのまま 比較できるようにあえて使っていない。
実行: python3 GoF/patterns/Iterator/python/main.py
"""Iterator パターン: 集合体層 (Java版の Aggregate.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from iterator import Iterator
class Aggregate(ABC):
"""「自分用のIteratorを作れる」ことだけを約束する抽象基底クラス。"""
@abstractmethod
def iterator(self) -> Iterator: ...
"""Iterator パターン: イテレータ層 (Java版の Iterator.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
Java版のIterator#next()はGenerics以前のJavaの制約でObjectを返し、呼び出し側で
キャストしている(Main.javaの (Book)it.next())。PythonにはJavaのfor文と統合された
__iter__/__next__プロトコル(StopIteration送出でfor文が止まる)もあるが、ここでは
Java版のAggregate/Iteratorインタフェース構造をそのまま素直に移植する方針のため、
あえて使わない。has_next()/next()という明示的なメソッドを持つIteratorクラスとして書く
(Pythonは動的型付けなので、next()の戻り値をJava版のようにAnyでキャストし直す必要はない
- BookShelfIteratorはより具体的にBookを返す)。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any
class Iterator(ABC):
@abstractmethod
def has_next(self) -> bool: ...
@abstractmethod
def next(self) -> Any: ...
"""Book層 (Java版の Book.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
class Book:
def __init__(self, name: str) -> None:
self._name = name
def get_name(self) -> str:
return self._name
"""BookShelf層 (Java版の BookShelf.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、Pythonではlistで
足りるので、Composite版のDirectoryと同様にmaxsize引数を外している。
"""
from __future__ import annotations
from aggregate import Aggregate
from book import Book
from book_shelf_iterator import BookShelfIterator
from iterator import Iterator
class BookShelf(Aggregate):
"""AggregateのConcreteAggregate。"""
def __init__(self) -> None:
self._books: list[Book] = []
def get_book_at(self, index: int) -> Book:
return self._books[index]
def append_book(self, book: Book) -> None:
self._books.append(book)
def get_length(self) -> int:
return len(self._books)
def iterator(self) -> Iterator:
"""Java版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。"""
return BookShelfIterator(self)
"""BookShelfIterator層 (Java版の BookShelfIterator.java 相当)
単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from book import Book
from iterator import Iterator
if TYPE_CHECKING:
# book_shelf.py が本モジュールを import するため、実行時 import は循環参照になる。
# `from __future__ import annotations` により型注釈は文字列として遅延評価されるので、
# 型チェッカ用にのみ import すれば実行時循環は起きない。
from book_shelf import BookShelf
class BookShelfIterator(Iterator):
"""IteratorのConcreteIterator。"""
def __init__(self, book_shelf: BookShelf) -> None:
self._book_shelf = book_shelf
self._index = 0
def has_next(self) -> bool:
return self._index < self._book_shelf.get_length()
def next(self) -> Book:
book = self._book_shelf.get_book_at(self._index)
self._index += 1
return book
"""Iterator パターン: 本棚から本を順番に取り出す (Java版Main.javaと同じお題)
実行: python3 main.py
(もしくはリポジトリルートから python3 GoF/patterns/Iterator/python/main.py)
Java版と同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。
"""
from __future__ import annotations
from book import Book
from book_shelf import BookShelf
def main() -> None:
book_shelf = BookShelf()
book_shelf.append_book(Book("Around the World in 80 Days"))
book_shelf.append_book(Book("Bible"))
book_shelf.append_book(Book("Cinderella"))
book_shelf.append_book(Book("Daddy-Long-Legs"))
it = book_shelf.iterator()
while it.has_next():
book = it.next()
print(book.get_name())
if __name__ == "__main__":
main()