概要
@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。
※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。
デザインパターンをなんで勉強したいのか?
- あちらこちらで使われている(気がする)ので、一度勉強したい
- 正しいコンテキストでコードを読み、修正できるようにする一助に
- インターフェース未だに腑に落ちない
- C#を触り始めて1年くらい経ってるのに...
- 「とりあえず動くコードやけど、似たような結果を期待したコードってたぶんいっぱいあるし、ベストプラクティス的なのありそう」と感じる局面が多い
- こういうときはこう書こうという引き出しを増やす。
- デザインパターンなんとなく勉強してみたい勢もいっぱいいるはずなので、何かしら提示してみたい
- How to study design patterns?
やっていく中で気を付けたいこと
一部デザインパターンはアンチパターンに転落していますが、他のデザインパターンは日常的に使用され、コードの適応性を向上させています。
と書いてあるので、使う上で気を付けるべきことがないか調べつつ書いていきたい(続けられる範囲で)。
- 読むだけだと頭に残らないかもしれないので、サンプルコードを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パターンを使ったループはI
Aggregate
の実装に依存しない。例えば、サンプルのBookShelf
クラスがList<T>
でなく、ArrayList
を使用する実装に変更されても、while部分は書き直さなくて済む。
使用上の注意
『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に書いてある内容にとても納得感がありました。
- 「次」は間違いやすい
IIterator
内のNext()
は現在の要素を返し、Index
を1進めるので、メソッド名を正確に書くとするとReturnCurrentElementAndAdvanceToNextPosition
になります。業務のコードで使われてるの見たことないけど、実際メソッド名は工夫して使うのかなぁ... - 「最後」も間違いやすい
IIterator
内のHasNext()
も、最後の要素を取得した後にHasNext()
がfalse
になることを注意して使わない実装・使用しないと「最後の要素だけ取り出し忘れてしまった...」ということになってしまいかねないですね。本に書いてある*「次にnextメソッドを呼んでも大丈夫かどうかを調べるもの」*という覚え方、よさげです。
関連しているパターン
記事にしたらリンクもつく予定...
- Visitorパターン
- Compositeパターン
- FactoryMethodパターン
感想や疑問
- いまだに
interface
、あれば使うものの、自分で1から書くときに使わなさそう(抽象クラスとの違いを理解してない?) - あるオブジェクトAから別のオブジェクトB(コレクション)のデータを利用するとき、Bの構造に依存せず利用したい...みたいな場面に出くわしたことない?のでありがたみをまだ実感できてないので、そういうときがきたら使ってみよう。
C#で学ぶデザインパターン入門
①Iterator
②Adapter
③Template Method
④Factory Method
⑤Singleton
⑥Prototype
⑦Builder
⑧AbstractFactory
⑨Bridge
⑩Strategy
⑪Composite Pattern
⑫Decorator Pattern