イテレータの利点
三日にわたって長々と説明してできたことは、たかだか配列をなめ回しただけじゃん、と思うかもしれない。main関数だけ見ればこれだけのことをするには、以下のコードで十分では?と思うだろう。
#include<stdio.h> #define ARRAY_SIZE_OF(array) ((sizeof(array)) / (sizeof(array[0]))) /*! Book構造体 */ typedef struct Book_tag { char *name; /*!< 書籍名 */ } Book; /* メイン関数 */ int main(int argc, char **argv) { int i; Book bookShelf[] = { {"Around the World in a Day"} , {"Bible"} , {"Cinderella"} , {"Daddy-Long-Leg"} }; for (i = 0; i < ARRAY_SIZE_OF(bookShelf); i++) { printf("%s\n", bookShelf[i].name); } return 0; }
もちろんサンプルコードは、サンプルでしかない。想定はBookShelfはプログラムのあらゆる場所から参照され、BookShelfの全要素をなめ回すコードがいくつもある状態だ。そのような場合にループの操作方法をインデックスの逆順にたどるように変更したい場合どうするか。
単なる配列をループで回すやり方では、プログラムのあらゆる場所のループ処理をすべて書き直さなければならない。イテレータパターンを採用していればコンクリートイテレータのhasNextとnextのみを書き換えるだけでよい
#include "BookShelfIterator.h" #include "BookShelf.h" #include "Book.h" #include <stdlib.h> struct Iterator_tag{ BookShelf *pBookShelf; int index; }; int hasNext(Iterator *p) { if (p->index >= 0) { return 1; } else { return 0; } } void *next(Iterator *p) { Book *pBook = GetBookAt(p->pBookShelf, p->index); p->index--; return pBook; } Iterator *CreateBookShelfIterator(BookShelf *pBookShelf) { Iterator *self = NULL; self = malloc(sizeof(Iterator)); if (self == NULL) { return NULL; } self->pBookShelf = pBookShelf; self->index = GetLength(pBookShelf) - 1; return self; } void DisposeBookShelfIterator(Iterator *p) { free(p); }
他のソースは全く書き換える必要は無いし、コンパイルの必要もない。リンクし直すだけでよい。ダイナミックリンクを利用している場合は該当dllを置き換えるだけで、リンクも不要だ。
さらにBookShelfは動的配列で実装しているが、結局インスタンス生成時に格納できる本の数が決まってしまう。もっと格納数に融通を利かせたくてリンクリストで実装し直したい場合も、mainは全く変更不要である。全要素のたぐり方は変わってしまうのでコンクリートイテレータは実装し直しになるが、それでもmain側は修正の必要ない。
さらにBook構造体のフィールドを増やし、ISBNコードや著者名を扱えるようにした場合でも、既存のフィールドとその操作関数に対しては、コードの修正は不要である。Book.hでは不完全型のみを公開し、実際のフィールド名は見えないようにしているからだ。