デザインパターンの一つ、Iteratorパターンを勉強してみました。
今回はQtとC++を使って、簡単な(無意味な)GUIアプリを作ってみました。使い勝手はわかったつもりですが、果たして使う時が作るのかは謎です。
謎なアプリを作ってしまった・・
Iteratorパターンとは
要素の集まり(集合体)を保有するオブジェクトの各要素に順番にアクセスする方法を提供するためのパターンです。
このパターンを使うと、いかなる集合体(std::vector, std::list, 二次元配列などなど)にアクセスする場合でも、比較的労力を使わずに簡単にアクセスをするプログラムを書くことが可能となります。
具体例を出します。例えば、以下のような3つの集合体があるとします。
- std::vector型で3つのAnimal型クラスを保持する集合体
- std::list型で3つのAnimal型クラスを保持する集合体
- 二次元配列で3つのAnimal型クラスを保持する集合体
それぞれ3つずつなんらかのAnimalクラスを保持しているので、それぞれ何を持っているのか出力したいと思います。
出力するためには、それぞれ違う型になるのでstd::vectorであれば、vector[index]でアクセスし、std::listだとこの方式は使えないので、ポインタを使って値にアクセスしなくてはいけません。
集合体ごとにアクセスするプログラムを作成しなくてはならないのは結構大変です。
しかし、Iteratorパターンを使えば、あるルールに乗っとるだけで、少しの実装量だけでこれら3つの集合体に簡単にアクセスできます。
そして、今後新たな型の集合体が追加されても柔軟に対応できるのが、このパターンの強みなのです。
では、そのルールとは何なのでしょうか?
クラス図
では、そのルールが何かを理解するためにクラス図を見てみましょう。
集合体はAggregateクラスを、アクセスする側はIteratorクラスをインタフェースとして持つことにより、ルールに乗ることができます。
その結果、ConcreteAggregateクラスとConcreteIteratorクラスが作成され、これらをmain関数から読み込むだけでどんな型のデータも大きな労力をかけずにアクセスするプログラムを作成することができます。
まだ分かりづらいと思うので、実際にアプリで動きを見てみます。
作成したアプリ
今回は、いかなる集合体にアクセスしても、比較的労力を使わない点を実感したかったので、Creatureクラスというものを作成し、
- std::vector型で3つのCreatureを保持する集合体
- std::list型で3つのCreatureを保持する集合体
- 二次元配列で3つのCreatureを保持する集合体
の3つの集合体クラスを作成し、それぞれ3つの集合体を出力するプログラムを作成しました。
コードと実行結果
何をしているかというと、画像左から、std::vector, std::list, 二次元配列で作成された集合体を表しています。
本当はそれぞれvector, list, 二次元配列っぽい画像にしたかったけど、いいのがなかった・・
クリックすると、その集合体に入っているデータが下部に表示されます。データは一緒ですが、それぞれ違う集合体に保存されています。(分かりづらいですね、ごめんなさい)
ちょっと意味のわからないアプリになってしまいましたが、イテレータパターンについての使い勝手はわかったつもりです。
コードを分解
では、一番左のMakimonoアイコン(std::vector型で3つのCreatureを保持する集合体
)をクリックしたときのコードを説明します。
mainwindow.h / mainwindow.cpp
ここで、集合体にデータを追加する作業や、アイコンがクリックされたらそれぞれの集合体からデータを読み込み表示する作業なんかをしています。
aggregate-interface.h
こちらはクラス図でいうところの、Aggregateクラスになります。
AggregateクラスはIterator関数を返すような仮想関数を定義しています。そのため、継承するクラスでoverrideして定義しなくてはいけません。
creature-vector.h / creature-vector.cpp
こちらはAggregateクラスを継承したConcreteAggregateクラスになります。
データのgetter/setterなどを定義します。そして、Iterator関数を持たなくてはいけないので、creature-vectorクラス用のIteratorクラスを返すようにしています。
iterator-interface.h
こちらはクラス図でいうところの、Iteratorクラスになります。
ここではデータを走査するための関数を仮想関数として定義しており、継承するクラスでこれらの関数をoverrideしなくてはいけません。
creature-vector-iterator.h / creature-vector-iterator.cpp
こちらはIteratorクラスを継承したConcreteIteratorクラスになります。
Iteratorクラスの仮想関数をoverrideして、データ走査のために必要な関数を定義します。
creature.cpp
こちらは、保存するデータのクラスになります。Creatureには、名前、鳴き声、食べ物というデータを持つようにしています。
クラス図を載せておきます。細かい関数などは記載していませんが、大体イメージはできるかなと思います。
その他のアイコンについても同じように、CreatureVector, CreatureVectorIteratorクラスに相当するものを作成しています。
このように、今後他の型の集合体を与えられた場合は、2クラスを追加するだけでデータ走査が簡単にできることになりました。
どういう時に使うのか?
実際に自分でアプリを作る前には、ありがたみがいまいちわからなかったのですが、実際に手を動かしてみて、このイテレータパターンを使うタイミングを理解できた気がします。
データを走査する必要があるときで、データがいろんな型で用意される場合にこれがズバリ使えるのでしょうか?
ってかそんなことあるのかなあ・・もしかしたら経験不足なだけで結構あるのかも・・
とりあえずデータ走査の必要が出てきた時はイテレータパターンを思い出せるようにしておきたいと思います!
まとめ
Iteratorパターンをアプリ作成してまとめてみました。使いどきはデータを走査したいときで、データがいろんな型で用意される場合があるときは、
こちらのパターンが使えるのではないでしょうか。
参考サイト
にほんブログ村に参加しております!よろしければクリックお願いします 🙂
にほんブログ村
コメント