C言語でIteratorパターン【オブジェクト指向】

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));
    }
}

 

目次:C言語でGoFのデザインパターン【オブジェクト指向】

ソースコード:https://github.com/yuksblog/c_gof_design_pattern