徒然電脳

日々のプログラミング(とその他)忘備録 このサイトは独自研究のみに基づきます マサカリ歓迎しますというかよろしくお願いします

デザインパターン[Iterator]

デザインパターン

 先日、結城浩先生の著書「増補改訂版 Java言語で学ぶ デザインパターン入門」を購入した。


www.amazon.co.jp

 しかし、恥ずかしながらJavaをほとんど触ったことがない。著書はJavaの文法をふんだんに使っているわけではないので理解が難しいことはまずないが、理解を確認するために別言語でもう一度書いておきたい。
 そこで、比較的馴染みのあるC#で書いて頭を整理することにした。じつは結城先生自身が、C MAGAZINEでの連載ですでにC#でのデザインパターンを書いていらっしゃったりもする。もはやブログで公開する意味もないと思いつつも、とりあえず忘備録として公開することにする。

ニュースの表示

 以前、RSSの取得プログラムを書いていた。いま見てみると、かなり汚いコードだがこれはIteratorパターンを適応できる。早速このネタで書いてみることにした。とりあえず適当なクラス図書いとく。
f:id:TempProg:20151218004703j:plain
 Iteratorパターンの、いわゆるAggregateインターフェイスIteratorインターフェイスにあたるのはそれぞれIEnumerableインターフェイス、IEnumeratorインターフェイス。これらはC#で用意され、これをIEnumerableを実装しているものはforeachで回すことが出来る。
 ConcreteAggregateクラス, ConcreteIteratorクラスが対応するのはそれぞれNewsListクラス, NewsEnumeratorクラス。Newsクラスはニュース単体を表し、NewsListクラスがその集合体(=Aggregate)を表している。

ソースコード

 以下にソースコードを示す。なお、GitHubにも同一のソースコードをアップロードした。
P-denshin/DesignPattern · GitHub


News.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class News {
        /// <summary>
        /// 記事のタイトル
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 記事のURL
        /// </summary>
        public string URL { get; set; }

        /// <summary>
        /// 記事の概要
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// ニュースを表示する。
        /// </summary>
        public void Show() {
            Console.WriteLine("タイトル:" + Title);
            Console.WriteLine("URL:" + URL);
            Console.WriteLine(Description);
        }
    }
}

NewsList.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class NewsList : IEnumerable<News> {
        List<News> newsList;

        /// <summary>
        /// 指定したインデックスにあるニュースを取得する。
        /// </summary>
        /// <param name="x">インデックス</param>
        /// <returns>指定したインデックスにある要素</returns>
        public News this[int x] {
            get {
                return newsList[x];
            }
        }

        /// <summary>
        /// ニュースの数を取得する。
        /// </summary>
        public int Length {
            get {
                return newsList.Count;
            }
        }

        /// <summary>
        /// ニュースを追加する。
        /// </summary>
        /// <param name="ns">追加するニュース</param>
        public void Add(News ns) {
            newsList.Add(ns);
        }

        /// <summary>
        /// ニュースリストを初期化する。
        /// </summary>
        public NewsList() {
            this.newsList = new List<News>();
        }

        /// <summary>
        /// ニュースリストの列挙子を取得する。
        /// </summary>
        /// <returns>ニュースリストの列挙子</returns>
        public IEnumerator<News> GetEnumerator() {
            return new NewsEnumerator(this);
        }

        /// <summary>
        /// ニュースリストの列挙子を取得する。
        /// </summary>
        /// <returns>ニュースリストの列挙子</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
}

 NewsList.csを見てもらえれば分かる通り、NewsListクラスはIEnumerableインターフェイスを実装する。IEnumerableインターフェイスはIEnumerableインターフェイス(非ジェネリック)を実装しているため、そちらもNewsListクラスで実装。ジェネリック版は非ジェネリック版と違い、拡張メソッドによってLINQの恩恵を得ることができる。

NewsEnumerator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class NewsEnumerator : IEnumerator<News> {
        private NewsList newsList;
        private int i;

        public NewsEnumerator(NewsList nl) {
            this.newsList = nl;
            i = -1;
        }

        /// <summary>
        /// 次のニュースを取得する。
        /// </summary>
        public News Current {
            get { return newsList[i]; }
        }

        //IEnumerator<out T>がIDisposableを実装してるためについてきたもの
        //Disposeの実装の必要はない(はず)
        public void Dispose() { }

        /// <summary>
        /// 次のニュースを取得する。
        /// </summary>
        object System.Collections.IEnumerator.Current {
            //IEnumerator<out T>がIEnumeratorを実装してるためについてきたもの
            get { return this.Current; }
        }


        /// <summary>
        /// 次のニュースに進める。
        /// </summary>
        /// <returns>次のニュースが存在すればtrue 次のニュースが存在しなければfalse</returns>
        public bool MoveNext() {
            i++;
            //範囲外
            if (i >= newsList.Length) {
                return false;
            }
            return true;
        }

        /// <summary>
        /// ニュース列挙子を初期化する。
        /// </summary>
        public void Reset() {
            i = -1;
        }
    }
}

 NewsEnumeratorクラスはIEnumeratorインターフェースを実装。IEnumeratorインターフェース自体はIDisposableインターフェース, IEnumeratorインターフェース(非ジェネリック)を実装。そのため、NewsEnumeratorは3つのインターフェースを実装している。実際に重要となるのはMoveNextメソッドとCurrentプロパティ(とついでにResetメソッド)である。Currentプロパティは「現在指し示すオブジェクト」を表している。MoveNextメソッドは「次に進める」処理と「次の存在の判定」処理を同時に行わなければいけない。「次」の状態が存在しない場合はfalseを返し、使用側にそれ以上の取得を行わないよう促す。
 意外と重要なのは初期状態で、IEnumeratorにおける初期状態は「最初の状態の前」である。なぜこのような仕様になっているかというと、MoveNextメソッドで次状態の存在を確認してから取得を行なうため初期状態が「最初の状態」に設定されていると、「最初のオブジェクト」が取得できないのである。

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class Program {
        static void Main(string[] args) {
            NewsList nl = new NewsList();

            nl.Add(
                new News() {
                    Title = "本日の天気",
                    URL = "http://www.~~~~.com/xxxxxx",
                    Description = "全国的に晴れやくもり、または雨でしょう"
                }
                );

            nl.Add(
                new News() {
                    Title = "ゆるゆりがすごい",
                    URL = "http://www.$$$$.com/xxxxx",
                    Description = "千鶴が可愛いと話題に"
                }
                );

            nl.Add(
                new News() {
                    Title = "ポテチが気が狂ってる",
                    URL = "http://www.%%%%%.com/xxxxx",
                    Description = "ショートケーキ味が期間限定で発売"
                }
                );

            IEnumerator<News> it = nl.GetEnumerator();
            while (it.MoveNext()) {
                it.Current.Show();
                Console.WriteLine();
            }


            foreach (News n in nl) {
                n.Show();
                Console.WriteLine();
            }
        }
    }
}

 最後に、使用方法としてMainメソッドを定義したProgram.csである。Mainメソッドの最後にwhileでの書き方とforeachでの書き方を示している。foreachで書いたものは、whileで書いたものと似たような形に展開される(実際は違う)。