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プロジェクト内に限った話でも、抽象度を高くすることで、随分と保守性が上がります。


ようは、他のクラス(集合の利用側)から見て、その集合がどう実装されているか、なんてどうでも良いんですよね。
これまでのプログラミングだと、どう実装されているかまで知らなくっちゃいけなかったものが、このパターンだと、数えてくれる人がいるので、利用側は「しっかり正確に数え上げます」という責任を持たなくて済むのが一番の利点です、というところでしょうか。(実装の隠匿)