Edited at

C#で学ぶデザインパターン入門 ①Iterator

More than 1 year has passed since last update.


概要

@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。

※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。


デザインパターンをなんで勉強したいのか?


  • あちらこちらで使われている(気がする)ので、一度勉強したい


    • 正しいコンテキストでコードを読み、修正できるようにする一助に



  • インターフェース未だに腑に落ちない


    • C#を触り始めて1年くらい経ってるのに...



  • 「とりあえず動くコードやけど、似たような結果を期待したコードってたぶんいっぱいあるし、ベストプラクティス的なのありそう」と感じる局面が多い


    • こういうときはこう書こうという引き出しを増やす。



  • デザインパターンなんとなく勉強してみたい勢もいっぱいいるはずなので、何かしら提示してみたい




やっていく中で気を付けたいこと


一部デザインパターンはアンチパターンに転落していますが、他のデザインパターンは日常的に使用され、コードの適応性を向上させています。


と書いてあるので、使う上で気を付けるべきことがないか調べつつ書いていきたい(続けられる範囲で)。


  • 読むだけだと頭に残らないかもしれないので、サンプルコードをC#で書き換えてみて、動作を確認する。

  • 考えたこととか、疑問点とかは残しておいて、公開した後でも追記・修正していく。


本題

Iteratorパターン

第1回はIteratorパターンです。Iteratorパターンはコレクションの要素にループ変数をiとしてfor文で順にアクセスしていく際の変数iの働きを抽象化し、一般化したものです。

コレクションの要素を列挙する手段を提供して、具体的な列挙方法をコレクションから隠ぺいすることができるんですね!


サンプルコード

早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。

// コンソールアプリケーションで実行を確認しました

static void Main(string[] args)
{
BookShelf 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"));
IIterator it = bookShelf.Iterator();
while(it.HasNext())
{
Book book = (Book)it.Next();
Console.WriteLine(book.Name);
}
// 実行結果
// Around the world in 80 days
// Bible
// Cinderella
// Daddy-Long-Legs

       // 実行が一瞬で終わって確認できないので、キーの入力を待ちます
Console.ReadLine();
}

// 集合体を表すインターフェース
public interface IAggregate
{
IIterator Iterator();
}

// 数え上げ、スキャンを表すインターフェース
public interface IIterator
{
bool HasNext();
object Next();
}

// 本を表すクラス
public class Book
{
public string Name { get; set; }
public Book(string name)
{
this.Name = name;
}
}

// 本棚を表すクラス
public class BookShelf : IAggregate
{
List<Book> books = new List<Book>();
int Last { get; set; } = 0;
public Book GetBookAt(int index)
{
return books[index];
}
public void appendBook(Book book)
{
this.books.Add(book);
Last++;
}
public int GetLength()
{
return this.Last;
}
public IIterator Iterator()
{
return new BookShelfIterator(this);
}
}

// 本棚をスキャンするクラス
public class BookShelfIterator : IIterator
{
BookShelf BookShelf { get; set; }
int Index { get; set; }
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);
this.Index++;
return book;
}
}


効能


  • Iteratorパターンを使ったループはIAggregateの実装に依存しない。例えば、サンプルのBookShelfクラスがList<T>でなく、ArrayListを使用する実装に変更されても、while部分は書き直さなくて済む。


使用上の注意

『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に書いてある内容にとても納得感がありました。


  • 「次」は間違いやすい

    IIterator内のNext()は現在の要素を返し、Indexを1進めるので、メソッド名を正確に書くとするとReturnCurrentElementAndAdvanceToNextPositionになります。業務のコードで使われてるの見たことないけど、実際メソッド名は工夫して使うのかなぁ...

  • 「最後」も間違いやすい

    IIterator内のHasNext()も、最後の要素を取得した後にHasNext()falseになることを注意して使わない実装・使用しないと「最後の要素だけ取り出し忘れてしまった...」ということになってしまいかねないですね。本に書いてある「次にnextメソッドを呼んでも大丈夫かどうかを調べるもの」という覚え方、よさげです。


関連しているパターン

記事にしたらリンクもつく予定...


感想や疑問


  • いまだにinterface、あれば使うものの、自分で1から書くときに使わなさそう(抽象クラスとの違いを理解してない?)

  • あるオブジェクトAから別のオブジェクトB(コレクション)のデータを利用するとき、Bの構造に依存せず利用したい...みたいな場面に出くわしたことない?のでありがたみをまだ実感できてないので、そういうときがきたら使ってみよう。


C#で学ぶデザインパターン入門

①Iterator

②Adapter

③Template Method

④Factory Method

⑤Singleton

⑥Prototype

⑦Builder

⑧AbstractFactory

⑨Bridge

⑩Strategy

⑪Composite Pattern

⑫Decorator Pattern