GoF Iteratorパターン
仕事が落ち着いてきたので、やっと再開できそうです。
最近は、デザインパターンの勉強を始めました。
(参考:増補改訂版Java言語で学ぶデザインパターン入門)
上、参考書籍ではJavaで書かれているのですが、それをC#で書きなおしてみました。
(URL先のページからサンプルソースをダウンロードすることができます。)
Iterator Pattern
Iteratorは「数え上げ」を行うパターンです。
Aggregateインタフェース
Aggregateとは、集合(体)を意味します。
namespace GoF.Iterator { interface IAggregate { IIterator iterator(); } }
Iteratorインタフェース
using System; namespace GoF.Iterator { public interface IIterator { bool hasNext(); Object next(); } }
AggregateとIteratorは対の関係にあります。
集合に対して、それを数え上げるIteratorがいる、それがこのパターンです。
二つに分けるミソは、Iteratorの種類をいくつ作っても、利用する側は(ほぼ)同じ処理で扱うことができるところですね。
Bookクラス
Javaのサンプルソースから引用したままですが、集合を構成する最小オブジェクトです。
namespace GoF.Iterator { class Book { private string name; public Book( string name ) { this.name = name; } public string getName() { return name; } } }
BookShelfクラス
上のBookオブジェクトの集合体です。(:本棚)
using System.Collections.Generic; namespace GoF.Iterator { class BookShelf : IAggregate { private List<Book> books; public BookShelf() { this.books = new List<Book>(); } public Book getBookAt( int index ) { return books[index]; } public void appendBook( Book book ) { this.books.Add( book ); } public int getLength() { return this.books.Count; } public IIterator iterator() { return new BookShelfIterator( this ); } } }
BookShelfIteratorクラス
本棚から本を1冊1冊数え上げる実体のクラスです。
using System; namespace GoF.Iterator { class BookShelfIterator : IIterator { private BookShelf bookShelf; private int index; public BookShelfIterator( BookShelf bookShelf ) { this.bookShelf = bookShelf; this.index = 0; } public bool hasNext() { if ( index < bookShelf.getLength() ) { return true; } else { return false; } } public Object next() { Book book = bookShelf.getBookAt( index ); index++; return book; } } }
これらを実際に使用したサンプルは次のとおり。
private void _BtnShow_Click( object sender, EventArgs e ) { this._RtxtList.Clear(); StringBuilder sb = new StringBuilder(); IIterator it = bookShelf.iterator(); while ( it.hasNext() ) { Book book = (Book)it.next(); sb.Append( book.getName() + "\n" ); } this._RtxtList.Text = sb.ToString(); } private void _BtnAdd_Click( object sender, EventArgs e ) { InputBoxForm frm = new InputBoxForm(); frm.ShowDialog(); string title = frm.Value; if ( title.Length > 0 ) { bookShelf.appendBook( new Book( title ) ); } }
(_RtxtListは、フォームに貼り付けてあるリッチテキストコントロールです。Addボタンを押すと本を追加し、Showボタンを押すことによって、現在の本棚にある本のタイトルを列挙します。)
一見すると、すごく回りくどいものに見えるのですが、オブジェクト指向の「再利用」を顕著に表したパターンだと思います。
このサンプルでは、Iteratorインタフェースを継承するクラス(BookShelfIterator)が一つしか無いですが、例えば、BookShelfを逆順に数え上げたり、タイトルを辞書順に数え上げたり、というIteratorを作ったとしても、BookShelfクラスのiteratorメソッドで返すインスタンスを変えるだけで、他は何も変える必要がありません。
また、サンプルソースでは、元々BookShelfはリストではなく、配列で作られていましたが、これをリストに変える過程でも、BookShelfクラスだけを変更するだけで済みました。
C#だとインデクサがあるので、return books[index]に変わりはないんですけど、appendだとか、言語によってはアクセスの仕方が全然変わりますし、やっぱりオブジェクト指向の抽象度の高さは流石だなと思います。
これがベタ書きのプログラムだと、数え上げる処理なんてあちらこちらに分散します。
その点、Iteratorパターンのように、クラスの中だけで完結してしまえば、(AggregateとIteratorの結合度は高いにしても、)外と内をしっかり区別することができます。
「再利用」って聞くと、プロジェクトをまたいで、どんなプログラムにも再利用できる、って思ってしまうんですが、1プロジェクト内に限った話でも、抽象度を高くすることで、随分と保守性が上がります。
ようは、他のクラス(集合の利用側)から見て、その集合がどう実装されているか、なんてどうでも良いんですよね。
これまでのプログラミングだと、どう実装されているかまで知らなくっちゃいけなかったものが、このパターンだと、数えてくれる人がいるので、利用側は「しっかり正確に数え上げます」という責任を持たなくて済むのが一番の利点です、というところでしょうか。(実装の隠匿)