C言語でのデザインパターンの実装方法完全ガイド

デザインパターンはソフトウェア設計の重要な要素です。これらのパターンは、再利用可能で効率的なコードを書くためのガイドラインを提供します。本記事では、C言語での代表的なデザインパターンの実装方法を詳細に解説します。これにより、読者はC言語でのデザインパターンの理解を深め、自身のプロジェクトに応用できるようになります。

目次
  1. シングルトンパターンの実装
    1. シングルトンパターンの基本概念
    2. C言語でのシングルトンパターンの実装
  2. ファクトリーパターンの実装
    1. ファクトリーパターンの基本概念
    2. C言語でのファクトリーパターンの実装
  3. ストラテジーパターンの実装
    1. ストラテジーパターンの基本概念
    2. C言語でのストラテジーパターンの実装
  4. オブザーバーパターンの実装
    1. オブザーバーパターンの基本概念
    2. C言語でのオブザーバーパターンの実装
  5. デコレーターパターンの実装
    1. デコレーターパターンの基本概念
    2. C言語でのデコレーターパターンの実装
  6. コンポジットパターンの実装
    1. コンポジットパターンの基本概念
    2. C言語でのコンポジットパターンの実装
  7. アダプターパターンの実装
    1. アダプターパターンの基本概念
    2. C言語でのアダプターパターンの実装
  8. ブリッジパターンの実装
    1. ブリッジパターンの基本概念
    2. C言語でのブリッジパターンの実装
  9. 演習問題
    1. シングルトンパターンの演習問題
    2. ファクトリーパターンの演習問題
    3. ストラテジーパターンの演習問題
    4. オブザーバーパターンの演習問題
  10. まとめ
    1. シングルトンパターン
    2. ファクトリーパターン
    3. ストラテジーパターン
    4. オブザーバーパターン
    5. デコレーターパターン
    6. コンポジットパターン
    7. アダプターパターン
    8. ブリッジパターン

シングルトンパターンの実装

シングルトンパターンは、あるクラスのインスタンスが1つしか存在しないことを保証するデザインパターンです。このパターンは、設定オブジェクトやロギング機能など、アプリケーション全体で1つのインスタンスが必要な場合に有用です。

シングルトンパターンの基本概念

シングルトンパターンでは、クラス自体がその唯一のインスタンスを管理し、そのインスタンスを返すメソッドを提供します。このメソッドは、初めて呼び出されたときにインスタンスを作成し、以降の呼び出しではそのインスタンスを返します。

C言語でのシングルトンパターンの実装

C言語でシングルトンパターンを実装するには、以下のような手順を踏みます。

1. シングルトンの構造体を定義する

typedef struct {
    int some_value;
    // 他のメンバ変数
} Singleton;

2. シングルトンインスタンスへのアクセス関数を定義する

Singleton* getInstance() {
    static Singleton instance;
    static int initialized = 0;

    if (!initialized) {
        // 初期化処理
        instance.some_value = 0;
        // 他の初期化コード
        initialized = 1;
    }

    return &instance;
}

3. シングルトンの使用方法

int main() {
    Singleton* singleton = getInstance();
    singleton->some_value = 42;

    Singleton* singleton2 = getInstance();
    printf("Value: %d\n", singleton2->some_value); // 42が出力される

    return 0;
}

このようにして、C言語でシングルトンパターンを実装できます。このパターンを使用することで、特定のクラスのインスタンスが1つだけ存在することを保証し、コードの一貫性と管理のしやすさを向上させます。

ファクトリーパターンの実装

ファクトリーパターンは、オブジェクトの生成を専門化した関数やメソッドに任せるデザインパターンです。これにより、オブジェクト生成の詳細をクライアントコードから隠し、コードの柔軟性と再利用性を高めることができます。

ファクトリーパターンの基本概念

ファクトリーパターンでは、特定のインターフェースやクラスのインスタンスを作成するためのファクトリーメソッドを使用します。これにより、クライアントコードは具体的なクラスに依存せずにオブジェクトを生成できます。

C言語でのファクトリーパターンの実装

C言語でファクトリーパターンを実装するには、以下の手順を踏みます。

1. 抽象型(インターフェース)を定義する

typedef struct {
    void (*doSomething)(void);
} Product;

2. 具体的な製品型を定義する

typedef struct {
    Product base;
    int specific_data;
} ConcreteProduct;

void concreteProductDoSomething() {
    printf("ConcreteProduct does something\n");
}

3. 具体的な製品を初期化する関数を定義する

ConcreteProduct* createConcreteProduct() {
    ConcreteProduct* product = (ConcreteProduct*)malloc(sizeof(ConcreteProduct));
    product->base.doSomething = concreteProductDoSomething;
    product->specific_data = 42;
    return product;
}

4. ファクトリ関数を定義する

Product* createProduct(const char* type) {
    if (strcmp(type, "ConcreteProduct") == 0) {
        return (Product*)createConcreteProduct();
    }
    // 他の製品タイプの生成コード
    return NULL;
}

5. ファクトリパターンの使用方法

int main() {
    Product* product = createProduct("ConcreteProduct");
    if (product) {
        product->doSomething();
    }

    free(product); // メモリの解放
    return 0;
}

このように、C言語でファクトリーパターンを実装することで、オブジェクト生成の詳細を隠蔽し、コードの柔軟性とメンテナンス性を向上させることができます。

ストラテジーパターンの実装

ストラテジーパターンは、アルゴリズムをクラスの内部にカプセル化し、同じ問題を解決する複数のアルゴリズムを簡単に交換できるようにするデザインパターンです。これにより、クライアントコードは異なるアルゴリズムを簡単に切り替えることができます。

ストラテジーパターンの基本概念

ストラテジーパターンでは、異なるアルゴリズムをそれぞれクラスとして実装し、コンテキストクラスがこれらのアルゴリズムを使用するようにします。アルゴリズムの選択は実行時に行われるため、クライアントコードは柔軟にアルゴリズムを変更できます。

C言語でのストラテジーパターンの実装

C言語でストラテジーパターンを実装するには、以下の手順を踏みます。

1. ストラテジーのインターフェースを定義する

typedef struct {
    void (*execute)(void);
} Strategy;

2. 具体的なストラテジーを定義する

void strategyAExecute() {
    printf("Strategy A is executed.\n");
}

void strategyBExecute() {
    printf("Strategy B is executed.\n");
}

Strategy strategyA = { strategyAExecute };
Strategy strategyB = { strategyBExecute };

3. コンテキストクラスを定義する

typedef struct {
    Strategy* strategy;
} Context;

void contextSetStrategy(Context* context, Strategy* strategy) {
    context->strategy = strategy;
}

void contextExecuteStrategy(Context* context) {
    context->strategy->execute();
}

4. ストラテジーパターンの使用方法

int main() {
    Context context;

    contextSetStrategy(&context, &strategyA);
    contextExecuteStrategy(&context); // "Strategy A is executed." が出力される

    contextSetStrategy(&context, &strategyB);
    contextExecuteStrategy(&context); // "Strategy B is executed." が出力される

    return 0;
}

このように、C言語でストラテジーパターンを実装することで、異なるアルゴリズムを簡単に交換できる柔軟な設計が可能になります。これにより、クライアントコードは特定のアルゴリズムに依存せず、要件に応じてアルゴリズムを変更できます。

オブザーバーパターンの実装

オブザーバーパターンは、オブジェクトが状態を変えるたびに、そのオブジェクトに依存する他のオブジェクトに通知を送るデザインパターンです。これにより、オブジェクト間の疎結合を実現し、システムの柔軟性と拡張性を高めることができます。

オブザーバーパターンの基本概念

オブザーバーパターンでは、サブジェクト(通知を送る側)とオブザーバー(通知を受け取る側)の関係を定義します。サブジェクトは、自身の状態が変化したときに、登録されたオブザーバーに通知を送ります。オブザーバーは、その通知を受け取って適切な処理を行います。

C言語でのオブザーバーパターンの実装

C言語でオブザーバーパターンを実装するには、以下の手順を踏みます。

1. オブザーバーのインターフェースを定義する

typedef struct Observer {
    void (*update)(struct Observer* self, int state);
    struct Observer* next;
} Observer;

2. サブジェクトの構造体を定義する

typedef struct {
    Observer* observers;
    int state;
} Subject;

void subjectAttach(Subject* subject, Observer* observer) {
    observer->next = subject->observers;
    subject->observers = observer;
}

void subjectDetach(Subject* subject, Observer* observer) {
    Observer** current = &subject->observers;
    while (*current != NULL) {
        if (*current == observer) {
            *current = observer->next;
            return;
        }
        current = &(*current)->next;
    }
}

void subjectNotify(Subject* subject) {
    Observer* observer = subject->observers;
    while (observer != NULL) {
        observer->update(observer, subject->state);
        observer = observer->next;
    }
}

3. 具体的なオブザーバーを定義する

typedef struct {
    Observer base;
    char name[50];
} ConcreteObserver;

void concreteObserverUpdate(Observer* self, int state) {
    ConcreteObserver* observer = (ConcreteObserver*)self;
    printf("%s received state %d\n", observer->name, state);
}

ConcreteObserver* createConcreteObserver(const char* name) {
    ConcreteObserver* observer = (ConcreteObserver*)malloc(sizeof(ConcreteObserver));
    observer->base.update = concreteObserverUpdate;
    strcpy(observer->name, name);
    return observer;
}

4. オブザーバーパターンの使用方法

int main() {
    Subject subject = { NULL, 0 };

    ConcreteObserver* observer1 = createConcreteObserver("Observer 1");
    ConcreteObserver* observer2 = createConcreteObserver("Observer 2");

    subjectAttach(&subject, (Observer*)observer1);
    subjectAttach(&subject, (Observer*)observer2);

    subject.state = 1;
    subjectNotify(&subject); // Observer 1とObserver 2に通知

    subjectDetach(&subject, (Observer*)observer1);
    subject.state = 2;
    subjectNotify(&subject); // Observer 2にのみ通知

    free(observer1);
    free(observer2);

    return 0;
}

このように、C言語でオブザーバーパターンを実装することで、オブジェクト間の疎結合を実現し、システムの柔軟性と拡張性を高めることができます。サブジェクトの状態変化をオブザーバーに通知する仕組みを構築することで、リアルタイムな状態管理が可能になります。

デコレーターパターンの実装

デコレーターパターンは、オブジェクトに動的に機能を追加するためのデザインパターンです。このパターンを使用すると、継承を使用せずにオブジェクトの振る舞いを拡張できます。

デコレーターパターンの基本概念

デコレーターパターンでは、オブジェクトをラップするデコレータを作成します。デコレータは、元のオブジェクトと同じインターフェースを実装し、元のオブジェクトへの参照を保持します。これにより、追加の機能を提供しながら、元のオブジェクトのメソッドを呼び出すことができます。

C言語でのデコレーターパターンの実装

C言語でデコレーターパターンを実装するには、以下の手順を踏みます。

1. コンポーネントのインターフェースを定義する

typedef struct {
    void (*operation)(void*);
} Component;

2. 具体的なコンポーネントを定義する

typedef struct {
    Component base;
} ConcreteComponent;

void concreteComponentOperation(void* self) {
    printf("ConcreteComponent operation\n");
}

ConcreteComponent* createConcreteComponent() {
    ConcreteComponent* component = (ConcreteComponent*)malloc(sizeof(ConcreteComponent));
    component->base.operation = concreteComponentOperation;
    return component;
}

3. デコレータのインターフェースを定義する

typedef struct {
    Component base;
    Component* wrappedComponent;
} Decorator;

void decoratorOperation(void* self) {
    Decorator* decorator = (Decorator*)self;
    if (decorator->wrappedComponent) {
        decorator->wrappedComponent->operation(decorator->wrappedComponent);
    }
}

4. 具体的なデコレータを定義する

typedef struct {
    Decorator base;
} ConcreteDecorator;

void concreteDecoratorOperation(void* self) {
    // 追加機能
    printf("ConcreteDecorator additional operation\n");

    // 元のオブジェクトの操作
    decoratorOperation(self);
}

ConcreteDecorator* createConcreteDecorator(Component* component) {
    ConcreteDecorator* decorator = (ConcreteDecorator*)malloc(sizeof(ConcreteDecorator));
    decorator->base.base.operation = concreteDecoratorOperation;
    decorator->base.wrappedComponent = component;
    return decorator;
}

5. デコレーターパターンの使用方法

int main() {
    ConcreteComponent* component = createConcreteComponent();
    ConcreteDecorator* decorator = createConcreteDecorator((Component*)component);

    decorator->base.base.operation((void*)decorator);

    free(decorator);
    free(component);

    return 0;
}

このように、C言語でデコレーターパターンを実装することで、オブジェクトの機能を動的に拡張できます。このパターンを使用することで、継承の代わりにコンポジションを利用し、柔軟な設計を実現します。

コンポジットパターンの実装

コンポジットパターンは、オブジェクトをツリー構造として扱い、個々のオブジェクトとオブジェクトの集まりを同様に扱うためのデザインパターンです。このパターンは、部分と全体を同一視することで、階層構造を簡単に操作できるようにします。

コンポジットパターンの基本概念

コンポジットパターンでは、基本要素(リーフ)と複合要素(コンポジット)の両方が同じインターフェースを実装します。これにより、クライアントはリーフとコンポジットを区別せずに使用でき、階層構造を簡単に操作できます。

C言語でのコンポジットパターンの実装

C言語でコンポジットパターンを実装するには、以下の手順を踏みます。

1. コンポーネントのインターフェースを定義する

typedef struct Component {
    void (*operation)(struct Component* self);
} Component;

2. リーフを定義する

typedef struct {
    Component base;
} Leaf;

void leafOperation(Component* self) {
    printf("Leaf operation\n");
}

Leaf* createLeaf() {
    Leaf* leaf = (Leaf*)malloc(sizeof(Leaf));
    leaf->base.operation = leafOperation;
    return leaf;
}

3. コンポジットを定義する

typedef struct Composite {
    Component base;
    Component** children;
    int childCount;
} Composite;

void compositeOperation(Component* self) {
    Composite* composite = (Composite*)self;
    for (int i = 0; i < composite->childCount; i++) {
        composite->children[i]->operation(composite->children[i]);
    }
}

void addComponent(Composite* composite, Component* component) {
    composite->children = (Component**)realloc(composite->children, sizeof(Component*) * (composite->childCount + 1));
    composite->children[composite->childCount] = component;
    composite->childCount++;
}

Composite* createComposite() {
    Composite* composite = (Composite*)malloc(sizeof(Composite));
    composite->base.operation = compositeOperation;
    composite->children = NULL;
    composite->childCount = 0;
    return composite;
}

4. コンポジットパターンの使用方法

int main() {
    Composite* root = createComposite();

    Leaf* leaf1 = createLeaf();
    Leaf* leaf2 = createLeaf();

    Composite* subtree = createComposite();
    Leaf* leaf3 = createLeaf();

    addComponent(subtree, (Component*)leaf3);
    addComponent(root, (Component*)leaf1);
    addComponent(root, (Component*)leaf2);
    addComponent(root, (Component*)subtree);

    root->base.operation((Component*)root);

    free(leaf1);
    free(leaf2);
    free(leaf3);
    free(subtree->children);
    free(subtree);
    free(root->children);
    free(root);

    return 0;
}

このように、C言語でコンポジットパターンを実装することで、オブジェクトの階層構造を簡単に管理し操作できるようになります。このパターンを使用することで、部分と全体を同一視し、柔軟で拡張可能な設計を実現できます。

アダプターパターンの実装

アダプターパターンは、互換性のないインターフェースを持つクラス同士を接続するためのデザインパターンです。このパターンを使用すると、既存のクラスを変更することなく、クラスのインターフェースを変換できます。

アダプターパターンの基本概念

アダプターパターンでは、既存のインターフェースを必要なインターフェースに変換するアダプタークラスを作成します。これにより、互換性のないインターフェースを持つクラス同士が連携できるようになります。

C言語でのアダプターパターンの実装

C言語でアダプターパターンを実装するには、以下の手順を踏みます。

1. クライアントが必要とするターゲットインターフェースを定義する

typedef struct {
    void (*request)(void);
} Target;

2. 既存のインターフェースを持つアダプティ(適応されるクラス)を定義する

typedef struct {
    void (*specificRequest)(void);
} Adaptee;

void adapteeSpecificRequest() {
    printf("Adaptee specific request\n");
}

3. アダプターを定義し、ターゲットインターフェースを実装する

typedef struct {
    Target base;
    Adaptee* adaptee;
} Adapter;

void adapterRequest() {
    adapteeSpecificRequest();
}

Adapter* createAdapter(Adaptee* adaptee) {
    Adapter* adapter = (Adapter*)malloc(sizeof(Adapter));
    adapter->base.request = adapterRequest;
    adapter->adaptee = adaptee;
    return adapter;
}

4. アダプターパターンの使用方法

int main() {
    Adaptee* adaptee = (Adaptee*)malloc(sizeof(Adaptee));
    adaptee->specificRequest = adapteeSpecificRequest;

    Adapter* adapter = createAdapter(adaptee);

    adapter->base.request(); // "Adaptee specific request" が出力される

    free(adapter);
    free(adaptee);

    return 0;
}

このように、C言語でアダプターパターンを実装することで、互換性のないインターフェースを持つクラス同士を簡単に連携させることができます。これにより、既存のコードを変更することなく、新しい機能やインターフェースを追加できます。

ブリッジパターンの実装

ブリッジパターンは、抽象部分と実装部分を分離して独立に変更できるようにするデザインパターンです。このパターンを使用することで、クラスの階層をシンプルに保ちつつ、機能の拡張が容易になります。

ブリッジパターンの基本概念

ブリッジパターンでは、抽象部分と実装部分がそれぞれ独立したインターフェースを持ちます。抽象部分は実装部分への参照を持ち、実装部分の詳細を隠蔽します。これにより、実装の変更が抽象部分に影響を与えず、柔軟な設計が可能になります。

C言語でのブリッジパターンの実装

C言語でブリッジパターンを実装するには、以下の手順を踏みます。

1. 実装部分のインターフェースを定義する

typedef struct {
    void (*operationImpl)(void);
} Implementor;

2. 具体的な実装を定義する

typedef struct {
    Implementor base;
} ConcreteImplementorA;

void concreteImplementorAOperation() {
    printf("ConcreteImplementorA operation\n");
}

ConcreteImplementorA* createConcreteImplementorA() {
    ConcreteImplementorA* impl = (ConcreteImplementorA*)malloc(sizeof(ConcreteImplementorA));
    impl->base.operationImpl = concreteImplementorAOperation;
    return impl;
}

typedef struct {
    Implementor base;
} ConcreteImplementorB;

void concreteImplementorBOperation() {
    printf("ConcreteImplementorB operation\n");
}

ConcreteImplementorB* createConcreteImplementorB() {
    ConcreteImplementorB* impl = (ConcreteImplementorB*)malloc(sizeof(ConcreteImplementorB));
    impl->base.operationImpl = concreteImplementorBOperation;
    return impl;
}

3. 抽象部分のインターフェースを定義する

typedef struct {
    Implementor* impl;
} Abstraction;

void abstractionOperation(Abstraction* abstraction) {
    abstraction->impl->operationImpl();
}

4. 拡張された抽象部分を定義する

typedef struct {
    Abstraction base;
} RefinedAbstraction;

void refinedAbstractionOperation(RefinedAbstraction* refined) {
    abstractionOperation((Abstraction*)refined);
    printf("RefinedAbstraction additional operation\n");
}

RefinedAbstraction* createRefinedAbstraction(Implementor* impl) {
    RefinedAbstraction* refined = (RefinedAbstraction*)malloc(sizeof(RefinedAbstraction));
    refined->base.impl = impl;
    return refined;
}

5. ブリッジパターンの使用方法

int main() {
    ConcreteImplementorA* implA = createConcreteImplementorA();
    ConcreteImplementorB* implB = createConcreteImplementorB();

    RefinedAbstraction* abstractionA = createRefinedAbstraction((Implementor*)implA);
    RefinedAbstraction* abstractionB = createRefinedAbstraction((Implementor*)implB);

    refinedAbstractionOperation(abstractionA); // "ConcreteImplementorA operation" と "RefinedAbstraction additional operation" が出力される
    refinedAbstractionOperation(abstractionB); // "ConcreteImplementorB operation" と "RefinedAbstraction additional operation" が出力される

    free(abstractionA);
    free(abstractionB);
    free(implA);
    free(implB);

    return 0;
}

このように、C言語でブリッジパターンを実装することで、抽象部分と実装部分を独立に変更可能にし、柔軟で拡張性の高い設計が可能になります。これにより、コードの保守性と再利用性が向上します。

演習問題

各デザインパターンの理解を深めるための演習問題を提供します。これらの演習問題を解くことで、C言語でのデザインパターンの実装方法をさらに深く理解することができます。

シングルトンパターンの演習問題

問題

シングルトンパターンを使用して、ロギングシステムを実装してください。このシステムは、ログメッセージを1つのログファイルに書き込みます。

解答例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    FILE* logFile;
} Logger;

Logger* getLogger() {
    static Logger logger;
    static int initialized = 0;

    if (!initialized) {
        logger.logFile = fopen("log.txt", "a");
        initialized = 1;
    }

    return &logger;
}

void logMessage(const char* message) {
    Logger* logger = getLogger();
    fprintf(logger->logFile, "%s\n", message);
}

int main() {
    logMessage("This is a log message.");
    logMessage("This is another log message.");

    return 0;
}

ファクトリーパターンの演習問題

問題

ファクトリーパターンを使用して、異なる種類の図形(例えば、円形と矩形)を生成するプログラムを実装してください。

解答例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    void (*draw)(void);
} Shape;

typedef struct {
    Shape base;
    int radius;
} Circle;

void drawCircle() {
    printf("Drawing a Circle\n");
}

Circle* createCircle() {
    Circle* circle = (Circle*)malloc(sizeof(Circle));
    circle->base.draw = drawCircle;
    circle->radius = 10;
    return circle;
}

typedef struct {
    Shape base;
    int width, height;
} Rectangle;

void drawRectangle() {
    printf("Drawing a Rectangle\n");
}

Rectangle* createRectangle() {
    Rectangle* rectangle = (Rectangle*)malloc(sizeof(Rectangle));
    rectangle->base.draw = drawRectangle;
    rectangle->width = 20;
    rectangle->height = 10;
    return rectangle;
}

Shape* createShape(const char* type) {
    if (strcmp(type, "Circle") == 0) {
        return (Shape*)createCircle();
    } else if (strcmp(type, "Rectangle") == 0) {
        return (Shape*)createRectangle();
    }
    return NULL;
}

int main() {
    Shape* shape1 = createShape("Circle");
    Shape* shape2 = createShape("Rectangle");

    shape1->draw();
    shape2->draw();

    free(shape1);
    free(shape2);

    return 0;
}

ストラテジーパターンの演習問題

問題

ストラテジーパターンを使用して、異なるソートアルゴリズム(例えば、バブルソートとクイックソート)を実装し、実行時に切り替えるプログラムを作成してください。

解答例

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    void (*sort)(int*, int);
} SortStrategy;

void bubbleSort(int* array, int size) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

void quickSort(int* array, int low, int high) {
    if (low < high) {
        int pivot = array[high];
        int i = (low - 1);

        for (int j = low; j <= high - 1; j++) {
            if (array[j] < pivot) {
                i++;
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
        int temp = array[i + 1];
        array[i + 1] = array[high];
        array[high] = temp;
        quickSort(array, low, i);
        quickSort(array, i + 2, high);
    }
}

void quickSortWrapper(int* array, int size) {
    quickSort(array, 0, size - 1);
}

SortStrategy* createSortStrategy(void (*sortFunc)(int*, int)) {
    SortStrategy* strategy = (SortStrategy*)malloc(sizeof(SortStrategy));
    strategy->sort = sortFunc;
    return strategy;
}

int main() {
    int array[] = {3, 1, 4, 1, 5, 9, 2, 6, 5};
    int size = sizeof(array) / sizeof(array[0]);

    SortStrategy* strategy = createSortStrategy(bubbleSort);
    strategy->sort(array, size);

    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    strategy->sort = quickSortWrapper;
    strategy->sort(array, size);

    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    free(strategy);
    return 0;
}

オブザーバーパターンの演習問題

問題

オブザーバーパターンを使用して、温度センサーの値を監視し、温度が変化するたびに複数の表示装置に通知するプログラムを実装してください。

解答例

#include <stdio.h>
#include <stdlib.h>

typedef struct Observer {
    void (*update)(struct Observer* self, int temperature);
    struct Observer* next;
} Observer;

typedef struct {
    Observer* observers;
    int temperature;
} Subject;

void subjectAttach(Subject* subject, Observer* observer) {
    observer->next = subject->observers;
    subject->observers = observer;
}

void subjectDetach(Subject* subject, Observer* observer) {
    Observer** current = &subject->observers;
    while (*current != NULL) {
        if (*current == observer) {
            *current = observer->next;
            return;
        }
        current = &(*current)->next;
    }
}

void subjectNotify(Subject* subject) {
    Observer* observer = subject->observers;
    while (observer != NULL) {
        observer->update(observer, subject->temperature);
        observer = observer->next;
    }
}

typedef struct {
    Observer base;
    char name[50];
} DisplayDevice;

void displayDeviceUpdate(Observer* self, int temperature) {
    DisplayDevice* display = (DisplayDevice*)self;
    printf("%s: Temperature is %d\n", display->name, temperature);
}

DisplayDevice* createDisplayDevice(const char* name) {
    DisplayDevice* display = (DisplayDevice*)malloc(sizeof(DisplayDevice));
    display->base.update = displayDeviceUpdate;
    strcpy(display->name, name);
    return display;
}

int main() {
    Subject subject = { NULL, 0 };

    DisplayDevice* display1 = createDisplayDevice("Display 1");
    DisplayDevice* display2 = createDisplayDevice("Display 2");

    subjectAttach(&subject, (Observer*)display1);
    subjectAttach(&subject, (Observer*)display2);

    subject.temperature = 25;
    subjectNotify(&subject);

    subject.temperature = 30;
    subjectNotify(&subject);

    subjectDetach(&subject, (Observer*)display1);
    subject.temperature = 35;
    subjectNotify(&subject);

    free(display1);
    free(display2);

    return 0;
}

これらの演習問題を通じて、各デザインパターンの具体的な実装方法を練習し、理解を深めることができます。演習問題に取り組むことで、実際のプロジェクトでの応用力が向上します。

まとめ

本記事では、C言語での代表的なデザインパターンの実装方法を詳細に解説しました。デザインパターンは、ソフトウェア設計の重要な要素であり、効率的で再利用可能なコードを書くためのガイドラインを提供します。以下に各パターンのポイントをまとめます。

シングルトンパターン

シングルトンパターンは、特定のクラスのインスタンスが1つしか存在しないことを保証するパターンです。これにより、グローバルなアクセス点を提供し、インスタンス管理を簡素化します。

ファクトリーパターン

ファクトリーパターンは、オブジェクトの生成を専門化した関数やメソッドに任せるパターンです。これにより、クライアントコードからオブジェクト生成の詳細を隠蔽し、コードの柔軟性と再利用性を高めます。

ストラテジーパターン

ストラテジーパターンは、アルゴリズムをクラスの内部にカプセル化し、同じ問題を解決する複数のアルゴリズムを簡単に交換できるようにするパターンです。これにより、クライアントコードは異なるアルゴリズムを簡単に切り替えることができます。

オブザーバーパターン

オブザーバーパターンは、オブジェクトが状態を変えるたびに、そのオブジェクトに依存する他のオブジェクトに通知を送るパターンです。これにより、オブジェクト間の疎結合を実現し、システムの柔軟性と拡張性を高めることができます。

デコレーターパターン

デコレーターパターンは、オブジェクトに動的に機能を追加するためのパターンです。継承を使用せずにオブジェクトの振る舞いを拡張でき、柔軟な設計が可能になります。

コンポジットパターン

コンポジットパターンは、オブジェクトをツリー構造として扱い、個々のオブジェクトとオブジェクトの集まりを同様に扱うパターンです。これにより、階層構造を簡単に操作できるようになります。

アダプターパターン

アダプターパターンは、互換性のないインターフェースを持つクラス同士を接続するためのパターンです。既存のクラスを変更することなく、クラスのインターフェースを変換できます。

ブリッジパターン

ブリッジパターンは、抽象部分と実装部分を分離して独立に変更できるようにするパターンです。これにより、クラスの階層をシンプルに保ちつつ、機能の拡張が容易になります。

これらのデザインパターンを理解し、適切に実装することで、C言語でのソフトウェア設計がより効率的で拡張性の高いものになります。デザインパターンの理解を深め、実際のプロジェクトに応用することで、優れたソフトウェアを構築できるようになるでしょう。

コメント

コメントする

目次
  1. シングルトンパターンの実装
    1. シングルトンパターンの基本概念
    2. C言語でのシングルトンパターンの実装
  2. ファクトリーパターンの実装
    1. ファクトリーパターンの基本概念
    2. C言語でのファクトリーパターンの実装
  3. ストラテジーパターンの実装
    1. ストラテジーパターンの基本概念
    2. C言語でのストラテジーパターンの実装
  4. オブザーバーパターンの実装
    1. オブザーバーパターンの基本概念
    2. C言語でのオブザーバーパターンの実装
  5. デコレーターパターンの実装
    1. デコレーターパターンの基本概念
    2. C言語でのデコレーターパターンの実装
  6. コンポジットパターンの実装
    1. コンポジットパターンの基本概念
    2. C言語でのコンポジットパターンの実装
  7. アダプターパターンの実装
    1. アダプターパターンの基本概念
    2. C言語でのアダプターパターンの実装
  8. ブリッジパターンの実装
    1. ブリッジパターンの基本概念
    2. C言語でのブリッジパターンの実装
  9. 演習問題
    1. シングルトンパターンの演習問題
    2. ファクトリーパターンの演習問題
    3. ストラテジーパターンの演習問題
    4. オブザーバーパターンの演習問題
  10. まとめ
    1. シングルトンパターン
    2. ファクトリーパターン
    3. ストラテジーパターン
    4. オブザーバーパターン
    5. デコレーターパターン
    6. コンポジットパターン
    7. アダプターパターン
    8. ブリッジパターン