C言語でGoFのIteratorパターンを実装します。
目次
Iteratorパターンを確認する
まずはIteratorパターンを確認しましょう。TECHSCOREさんのIteratorパターンの解説記事をご参照ください。
https://www.techscore.com/tech/DesignPattern/Iterator/Iterator1.html/
Iteratorパターンで設計する
TECHSCOREさんのサンプルコードをC言語に移植します。移植にあたり、下記のようなクラス図を書いてみました。
まず、生徒名簿についてですが、C言語におけるリスト表現としては配列と連結リストがあると思います。まずは配列で実現してみて、後で連結リスト版も作ってみましょう。
なので、生徒名簿クラスはStudentArrayListという名前とし、それに伴いIteratorはStudentArrayIteratorという名前にしました。
教師は配列版の生徒名簿を持つ人になってもらうので、名前をArrayTeacherとしておきましょう。
Iteratorパターンを実装する
Studentを作る
生徒がいないことには名簿を作れないので、ここから始めましょう。
Studentの定義はこんな感じになりますね。
Student.h
typedef struct StudentStruct Student; extern Student *Student_create(char name, uint8_t sex); extern void Student_destroy(Student *student); extern char Student_getName(Student *student); extern uint8_t Student_getSex(Student *student);
StudentStructはStudentPrivate.hで隠ぺいしています。
StudentPrivate.h
typedef struct StudentStruct { char name; uint8_t sex; } StudentStruct;
Iteratorを作る
生徒名簿の作成に移りたいところですが、その前にIteratorとAggregateのインタフェースを作っておく必要がありますね。まずはIteratorです。
Iterator.h
typedef struct Iterator { bool (*hasNext)(struct Iterator *ite); void *(*next)(struct Iterator *ite); } Iterator; extern bool Iterator_hasNext(Iterator* ite); extern void *Iterator_next(Iterator* ite);
hasNextはそのままですね。
nextは、返す値がここではわからないのでvoid*にしておくのがポイントです。
Aggregateを作る
次はAggregateです。特に言うことはありませんが、大丈夫ですよね。
Aggregate.h
typedef struct Aggregate { Iterator *(*iterator)(struct Aggregate *agr); } Aggregate; extern Iterator *Aggregate_iterator(Aggregate *agr);
StudentArrayListを作る
生徒とIteratorパターンのインタフェースも用意できたので、生徒名簿を作ってみましょう。
ヘッダファイルは次のように定義します。
StudentArrayList
typedef struct StudentArrayListStruct StudentArrayList; extern StudentArrayList *StudentArrayList_create(); extern void StudentArrayList_destroy(StudentArrayList *students); extern void StudentArrayList_addStudent(StudentArrayList *students, Student *student);
隠ぺい化したStudentArrayListStructを見てみましょう。
StudentArrayList.c
#define Student_MAX 10 typedef struct StudentArrayListStruct { Aggregate interface; Student *list[Student_MAX]; int16_t last; StudentArrayIterator *ite; } StudentArrayListStruct;
Aggregateインタフェースを実装するのでAggregateを頭に持ってきます。
生徒のリストを配列で格納するようにしています。
lastは最後の生徒を指し示すインデックスとして利用します。
生徒名簿のIteratorであるStudentArrayListIteratorは、iterator()の度に生成しても良いのですが、今回は直接保持する方式にしました。
これらメンバの初期化はcreate()の中で行います。
StudentArrayList.c
StudentArrayList *StudentArrayList_create() { StudentArrayList *students = (StudentArrayList*)malloc(sizeof(StudentArrayList)); students->interface.iterator = iterator; students->last = -1; students->ite = StudentArrayIterator_create((Student**)students->list); return students; }
Aggregateインタフェースのiteratorメソッドと、StudentArrayIteratorはまだ作ってないので、このままだとコンパイルエラーになっていしまいます。StudentArrayIteratorは後回しにして、まずはiteratorメソッドを作ります。
StudentArrayList.c
static Iterator *iterator(Aggregate *agr) { StudentArrayList *students = (StudentArrayList*)agr; StudentArrayIterator_reset(students->ite, students->last + 1); return (Iterator*)students->ite; }
StudentArrayIteratorをリセットして返しているだけです。リセットする理由は、StudentArrayIteratorの方で説明します。
その他のメソッドは次のように実装しておきます。
StudentArrayList.c
void StudentArrayList_destroy(StudentArrayList *students) { free(students); } void StudentArrayList_addStudent(StudentArrayList *students, Student *student) { students->last++; students->list[students->last] = student; }
StudentArrayListIteratorを作る
生徒名簿のIteratorを実装しましょう。ヘッダファイルはcreateとdestroyと、先ほど出てきたresetだけです。
StudentArrayListIterator.h
typedef struct StudentArrayIteratorStruct StudentArrayIterator; extern StudentArrayIterator *StudentArrayIterator_create(Student *list[]); extern void StudentArrayIterator_destroy(StudentArrayIterator *ite); extern void StudentArrayIterator_reset(StudentArrayIterator *ite, uint16_t size);
さて、順番に中身を見ていきましょう。まずは隠ぺい化したStudentArrayIteratorStructです。
StudentArrayIterator.c
typedef struct StudentArrayIteratorStruct { Iterator interface; Student **list; uint16_t size; uint16_t index; } StudentArrayIteratorStruct;
Iteratorインタフェースを実装するのでIteratorを頭にもってきます。
生徒名簿を持つ必要があるので、生徒の配列を持てるようにします。
sizeは生徒の数です。
indexはnext()の度にインクリメントするインデックスです。
StudentArrayListでリセットするのはsizeとindexを初期化するためです。sizeは生徒数に変更がある可能性があるので教えてあげる必要があり、indexは0に戻す必要があります。
これらのメンバの初期化はcreateメソッドで行います。
StudentArrayIterator.c
StudentArrayIterator *StudentArrayIterator_create(Student *list[]) { StudentArrayIterator *ite = (StudentArrayIterator*)malloc(sizeof(StudentArrayIterator)); ite->interface.hasNext = hasNext; ite->interface.next = next; ite->list = list; ite->size = 0; ite->index = 0; return ite; }
初期化してるだけなので特に何も言うことはありませんね。
Iteratorインタフェースの実装は次のようにします。簡単ですね。
StudentArrayIterator.c
static bool hasNext(Iterator *ite) { StudentArrayIterator *si = (StudentArrayIterator*)ite; bool ret = false; if (si->size > 0 && si->index < si->size) { ret = true; } return ret; } static void *next(Iterator *ite) { StudentArrayIterator* si = (StudentArrayIterator*)ite; Student *next = &(*si->list[si->index]); si->index++; return next; }
残りのメソッドは次のように実装しておきます。
StudentArrayIterator.c
void StudentArrayIterator_destroy(StudentArrayIterator *ite) { free (ite); } void StudentArrayIterator_reset(StudentArrayIterator *ite, uint16_t size) { ite->index = 0; ite->size = size; }
ArrayTeacherを作る
ここまでで生徒名簿を作ることができました。では、これを利用する教師を作っていきましょう。
まず、Teacherインタフェースは次のようになります。
Teacher.h
typedef struct Teacher { void (*createStudentList)(struct Teacher *teacher); void (*callStudents)(struct Teacher *teacher); } Teacher; extern void Teacher_createStudentList(Teacher *teacher); extern void Teacher_callStudents(Teacher *teacher);
これを実装するArrayTeacherを作ります。ここは一気にいきましょう。
ArrayTeacher.c
typedef struct ArrayTeacherStruct { Teacher interface; StudentArrayList *students; } ArrayTeacherStruct; static void createStudentList(Teacher *teacher) { ArrayTeacher *ta = (ArrayTeacher*)teacher; ta->students = StudentArrayList_create(); StudentArrayList_addStudent(ta->students, Student_create('A', 0)); StudentArrayList_addStudent(ta->students, Student_create('B', 1)); StudentArrayList_addStudent(ta->students, Student_create('C', 0)); StudentArrayList_addStudent(ta->students, Student_create('D', 1)); } static void callStudents(Teacher *teacher) { ArrayTeacher *ta = (ArrayTeacher*)teacher; Iterator *ite = Aggregate_iterator((Aggregate*)ta->students); while (Iterator_hasNext(ite)) { Student *student = (Student*)Iterator_next(ite); printf("Name=%c, Sex=%d\n", Student_getName(student), Student_getSex(student)); } } ArrayTeacher *ArrayTeacher_create() { ArrayTeacher *teacher = (ArrayTeacher*)malloc(sizeof(ArrayTeacher)); teacher->interface.createStudentList = createStudentList; teacher->interface.callStudents = callStudents; teacher->students = NULL; return teacher; } void ArrayTeacher_destroy(ArrayTeacher *ta) { if (ta->students != NULL) { Iterator *ite = Aggregate_iterator((Aggregate*)ta->students); while (Iterator_hasNext(ite)) { Student *student = (Student*)Iterator_next(ite); Student_destroy(student); } StudentArrayList_destroy(ta->students); } free(ta); }
ポイントはcallStudentsメソッドの内容ですね。Iteratorパターンがうまく適用されており、生徒名簿の実装が変わっても影響を受けにくいことが、なんとなくわかると思います。
別の実装でIteratorパターンを試す
上の例では名簿の実装方法は配列でした。配列ではなく連結リストにしてみるどどうなるか見てみましょう。
先に作る予定のプログラムのクラス図をお見せします。
Studentを改造する
連結リストにするためには次の要素への参照ポインタが必要になります。なのでStudentを改造して連結リストで使用可能なLinkedStudentを作りましょう。
LinkedStudentPrivate.h
typedef struct LinkedStudentStruct { Student super; struct LinkedStudentStruct *next; } StructLinkedStudent;
StudentクラスをextendしてLinkedStudentが作れました。このLinkedStudentクラスを使って、新しい名簿を実現するため、下記の2つのクラスを作っていきましょう。
- LinkedStudentList
- LinkedStudentIterator
LinkedStudentListを作る
LinkedStudentListは次のように作れます。配列ではなくて連結リストで生徒の名簿を保持するようになっています。
LinkedStudentList.c
typedef struct LinkedStudentListStruct { Aggregate interface; LinkedStudent *start; LinkedStudentIterator *ite; } LinkedStudentListStruct; static Iterator *iterator(Aggregate *agr) { LinkedStudentList *students = (LinkedStudentList*)agr; LinkedStudentIterator_reset(students->ite, students->start); return (Iterator*)students->ite; } LinkedStudentList *LinkedStudentList_create() { LinkedStudentList *list = (LinkedStudentList*)malloc(sizeof(LinkedStudentList)); list->interface.iterator = iterator; list->start = NULL; list->ite = LinkedStudentIterator_create(); return list; } void LinkedStudentList_destroy(LinkedStudentList *students) { free(students); } void LinkedStudentList_addStudent(LinkedStudentList *students, LinkedStudent *student) { if (students->start == NULL) { students->start = student; } else { LinkedStudent *tmp = students->start; while (tmp->next != NULL) { tmp = tmp->next; } tmp->next = student; } }
LinkedStudentIteratorを作る
LinkedStudentIteratorは次のように作れます。
LinkedStudentIterator.c
typedef struct LinkedStudentIteratorStruct { Iterator interface; LinkedStudent *current; } LinkedStudentIteratorStruct; static bool hasNext(Iterator *ite) { LinkedStudentIterator *sli = (LinkedStudentIterator*)ite; bool ret = false; if (sli->current != NULL) { ret = true; } return ret; } static void *next(Iterator *ite) { LinkedStudentIterator *sli = (LinkedStudentIterator*)ite; LinkedStudent *tmp = sli->current; sli->current = sli->current->next; return tmp; } LinkedStudentIterator *LinkedStudentIterator_create() { LinkedStudentIterator *ite = (LinkedStudentIterator*)malloc(sizeof(LinkedStudentIterator)); ite->interface.hasNext = hasNext; ite->interface.next = next; ite->current = NULL; return ite; } void LinkedStudentIterator_destroy(LinkedStudentIterator *ite) { free(ite); } void LinkedStudentIterator_reset(LinkedStudentIterator *ite, LinkedStudent *start) { ite->current = start; }
LinkTeacherを作る
連結リスト版の生徒名簿を使うLinkTeacherを作ってみます。ソースコードは次のようになります。
LinkTeacher.c
typedef struct LinkTeacherStruct { Teacher interface; LinkedStudentList *students; } LinkTeacherStruct; static void createStudentList(Teacher *teacher) { LinkTeacher *ta = (LinkTeacher*)teacher; ta->students = LinkedStudentList_create(); LinkedStudentList_addStudent(ta->students, LinkedStudent_create('1', 0)); LinkedStudentList_addStudent(ta->students, LinkedStudent_create('2', 1)); } static void callStudents(Teacher *teacher) { LinkTeacher *ta = (LinkTeacher*)teacher; Iterator *ite = Aggregate_iterator((Aggregate*)ta->students); while (Iterator_hasNext(ite)) { Student *student = (Student*)Iterator_next(ite); printf("Name=%c, Sex=%d\n", Student_getName(student), Student_getSex(student)); } } LinkTeacher *LinkTeacher_create() { LinkTeacher *ta = (LinkTeacher*)malloc(sizeof(LinkTeacher)); ta->interface.createStudentList = createStudentList; ta->interface.callStudents = callStudents; ta->students = NULL; return ta; } void LinkTeacher_destroy(LinkTeacher *teacher) { Iterator *ite = Aggregate_iterator((Aggregate*)teacher->students); while (Iterator_hasNext(ite)) { LinkedStudent *student = (LinkedStudent*)Iterator_next(ite); LinkedStudent_destroy(student); } LinkedStudentList_destroy(teacher->students); free(teacher); }
ポイントはやはりcallStudentsメソッドです。生徒名簿の実装が配列から連結リストになっても、callStudentsに影響がないことがわかると思います。
配列版
static void callStudents(Teacher *teacher) { ArrayTeacher *ta = (ArrayTeacher*)teacher; Iterator *ite = Aggregate_iterator((Aggregate*)ta->students); while (Iterator_hasNext(ite)) { Student *student = (Student*)Iterator_next(ite); printf("Name=%c, Sex=%d\n", Student_getName(student), Student_getSex(student)); } }
連結リスト版
static void callStudents(Teacher *teacher) { LinkTeacher *ta = (LinkTeacher*)teacher; Iterator *ite = Aggregate_iterator((Aggregate*)ta->students); while (Iterator_hasNext(ite)) { Student *student = (Student*)Iterator_next(ite); printf("Name=%c, Sex=%d\n", Student_getName(student), Student_getSex(student)); } }