C++で学ぶプロトタイプパターン:実装方法と応用例

デザインパターンはソフトウェア開発における共通の問題に対する汎用的な解決策です。その中でも、プロトタイプパターンは、オブジェクトのインスタンスを作成する際に新しいインスタンスを生成するのではなく、既存のインスタンスをコピーして作成する方法です。これは特に複雑なオブジェクトの作成コストを削減し、オブジェクトの複製が簡単に行えるという利点があります。本記事では、C++を用いたプロトタイプパターンの具体的な実装方法とその応用例について詳しく解説していきます。

目次

プロトタイプパターンとは

プロトタイプパターンは、オブジェクト指向デザインパターンの一つで、既存のオブジェクトをコピーして新しいオブジェクトを生成する方法です。このパターンは、特定の種類のオブジェクトを繰り返し作成する場合に非常に有効です。プロトタイプパターンの主な利点は次の通りです:

オブジェクト生成のコスト削減

新しいオブジェクトを一から生成するのではなく、既存のオブジェクトをコピーすることで、生成コストを削減します。特に、複雑なオブジェクトや初期化に時間がかかるオブジェクトに対して効果的です。

柔軟性の向上

プロトタイプパターンを使用すると、動的にオブジェクトの型を決定することができ、コードの柔軟性が向上します。これにより、動的なタイプのオブジェクト生成が必要な場合に役立ちます。

実装の簡便さ

プロトタイプパターンは、クラス階層を複雑にせずにオブジェクトのコピー機能を提供するため、シンプルで直感的な実装が可能です。これにより、コードの保守性も向上します。

このパターンを使用することで、複雑なオブジェクトの生成が効率的かつ効果的に行えるようになります。次に、C++での具体的なクラス構造と実装方法について見ていきます。

プロトタイプパターンのクラス構造

プロトタイプパターンをC++で実装する際には、以下のようなクラス構造を用います。基本的には、抽象クラスまたはインターフェースを使ってプロトタイプの基本形を定義し、具体的なクラスがそれを継承して実装します。

抽象クラス:Prototype

プロトタイプパターンの基礎となる抽象クラスです。このクラスには、オブジェクトのクローンを作成するための純粋仮想関数 clone() が定義されています。

class Prototype {
public:
    virtual ~Prototype() {}
    virtual Prototype* clone() const = 0;
};

具体クラス:ConcretePrototype1

このクラスは、具体的なオブジェクトのクローンを作成するための実装を持つクラスです。clone() メソッドをオーバーライドし、自身のインスタンスをコピーして返します。

class ConcretePrototype1 : public Prototype {
private:
    int data;
public:
    ConcretePrototype1(int d) : data(d) {}

    // コピーコンストラクタ
    ConcretePrototype1(const ConcretePrototype1& other) : data(other.data) {}

    Prototype* clone() const override {
        return new ConcretePrototype1(*this);
    }

    void show() {
        std::cout << "ConcretePrototype1 with data: " << data << std::endl;
    }
};

具体クラス:ConcretePrototype2

もう一つの具体的なプロトタイプクラスの例です。このクラスも同様に clone() メソッドを実装し、自身のコピーを作成します。

class ConcretePrototype2 : public Prototype {
private:
    std::string text;
public:
    ConcretePrototype2(std::string t) : text(t) {}

    // コピーコンストラクタ
    ConcretePrototype2(const ConcretePrototype2& other) : text(other.text) {}

    Prototype* clone() const override {
        return new ConcretePrototype2(*this);
    }

    void show() {
        std::cout << "ConcretePrototype2 with text: " << text << std::endl;
    }
};

プロトタイプパターンのクラス図

次に示すのは、プロトタイプパターンのクラス構造を示した図です:

Prototype
   |
   +---- ConcretePrototype1
   |
   +---- ConcretePrototype2

この構造を基に、クローンメソッドを実装してオブジェクトをコピーする方法について詳しく見ていきます。

クローンメソッドの実装

プロトタイプパターンにおいて、クローンメソッドはオブジェクトの複製を行う中心的な役割を果たします。C++でクローンメソッドを実装するためには、クラスのコピーコンストラクタと clone() メソッドを利用します。このセクションでは、具体的な実装方法について説明します。

コピーコンストラクタの定義

クローンメソッドを効果的に利用するために、クラスにはコピーコンストラクタを定義します。コピーコンストラクタは、既存のオブジェクトのデータを使って新しいオブジェクトを初期化します。

class ConcretePrototype1 : public Prototype {
private:
    int data;
public:
    ConcretePrototype1(int d) : data(d) {}

    // コピーコンストラクタ
    ConcretePrototype1(const ConcretePrototype1& other) : data(other.data) {}

    Prototype* clone() const override {
        return new ConcretePrototype1(*this);
    }

    void show() {
        std::cout << "ConcretePrototype1 with data: " << data << std::endl;
    }
};

クローンメソッドの実装

クローンメソッドは、自身のインスタンスを新たに作成し、そのコピーを返すメソッドです。上記の例では、clone() メソッドは new 演算子を使ってコピーコンストラクタを呼び出し、新しいインスタンスを生成します。

Prototype* ConcretePrototype1::clone() const {
    return new ConcretePrototype1(*this);
}

具体例:ConcretePrototype2

別の具体的なプロトタイプクラスにおいても、同様にコピーコンストラクタと clone() メソッドを実装します。

class ConcretePrototype2 : public Prototype {
private:
    std::string text;
public:
    ConcretePrototype2(std::string t) : text(t) {}

    // コピーコンストラクタ
    ConcretePrototype2(const ConcretePrototype2& other) : text(other.text) {}

    Prototype* clone() const override {
        return new ConcretePrototype2(*this);
    }

    void show() {
        std::cout << "ConcretePrototype2 with text: " << text << std::endl;
    }
};

クローンメソッドの動作確認

クローンメソッドを利用してオブジェクトのコピーを作成し、動作を確認します。

int main() {
    // オリジナルオブジェクトの作成
    ConcretePrototype1* original1 = new ConcretePrototype1(42);
    ConcretePrototype2* original2 = new ConcretePrototype2("Hello");

    // クローンオブジェクトの作成
    Prototype* clone1 = original1->clone();
    Prototype* clone2 = original2->clone();

    // 動作確認
    original1->show(); // Output: ConcretePrototype1 with data: 42
    clone1->show();    // Output: ConcretePrototype1 with data: 42

    original2->show(); // Output: ConcretePrototype2 with text: Hello
    clone2->show();    // Output: ConcretePrototype2 with text: Hello

    // メモリ解放
    delete original1;
    delete original2;
    delete clone1;
    delete clone2;

    return 0;
}

このように、プロトタイプパターンではコピーコンストラクタとクローンメソッドを組み合わせることで、簡単かつ効率的にオブジェクトの複製が可能になります。次に、具体的な図形クラスを例にして、プロトタイプパターンの応用を見ていきましょう。

具体例:図形のクローン

プロトタイプパターンの具体例として、図形クラスを用いた実装を紹介します。このセクションでは、基本的な図形クラスを作成し、それをプロトタイプとしてクローンする方法について説明します。

抽象クラス:Shape

まず、図形クラスの基底クラスとして Shape クラスを定義します。このクラスには、クローンメソッドの純粋仮想関数が含まれています。

class Shape {
public:
    virtual ~Shape() {}
    virtual Shape* clone() const = 0;
    virtual void draw() const = 0;
};

具体クラス:Circle

次に、具体的な図形クラス Circle を定義します。このクラスは、円の半径をメンバー変数として持ち、clone() メソッドと draw() メソッドを実装します。

class Circle : public Shape {
private:
    int radius;
public:
    Circle(int r) : radius(r) {}

    // コピーコンストラクタ
    Circle(const Circle& other) : radius(other.radius) {}

    Shape* clone() const override {
        return new Circle(*this);
    }

    void draw() const override {
        std::cout << "Circle with radius: " << radius << std::endl;
    }
};

具体クラス:Rectangle

同様に、矩形クラス Rectangle を定義します。このクラスは、幅と高さをメンバー変数として持ち、clone() メソッドと draw() メソッドを実装します。

class Rectangle : public Shape {
private:
    int width, height;
public:
    Rectangle(int w, int h) : width(w), height(h) {}

    // コピーコンストラクタ
    Rectangle(const Rectangle& other) : width(other.width), height(other.height) {}

    Shape* clone() const override {
        return new Rectangle(*this);
    }

    void draw() const override {
        std::cout << "Rectangle with width: " << width << ", height: " << height << std::endl;
    }
};

図形クラスのクローンと描画

これらのクラスを使って、図形オブジェクトのクローンを作成し、描画する例を示します。

int main() {
    // オリジナル図形オブジェクトの作成
    Shape* originalCircle = new Circle(10);
    Shape* originalRectangle = new Rectangle(5, 7);

    // クローン図形オブジェクトの作成
    Shape* cloneCircle = originalCircle->clone();
    Shape* cloneRectangle = originalRectangle->clone();

    // 描画の確認
    originalCircle->draw(); // Output: Circle with radius: 10
    cloneCircle->draw();    // Output: Circle with radius: 10

    originalRectangle->draw(); // Output: Rectangle with width: 5, height: 7
    cloneRectangle->draw();    // Output: Rectangle with width: 5, height: 7

    // メモリ解放
    delete originalCircle;
    delete originalRectangle;
    delete cloneCircle;
    delete cloneRectangle;

    return 0;
}

この例では、Circle クラスと Rectangle クラスがそれぞれプロトタイプパターンを実装しており、クローンメソッドを使ってオブジェクトを複製しています。これにより、図形オブジェクトの生成が効率的かつ簡単に行えます。次に、プロトタイプパターンにおける深いコピーと浅いコピーの違いについて説明します。

深いコピーと浅いコピーの違い

プロトタイプパターンを実装する際に考慮すべき重要な概念として、深いコピーと浅いコピーがあります。このセクションでは、これらの違いと、それぞれの利点と欠点について説明します。

浅いコピー

浅いコピーは、オブジェクトのメンバー変数のアドレスをコピーすることで、元のオブジェクトと同じメモリ空間を共有するコピー方法です。つまり、浅いコピーはポインタや参照をそのままコピーします。

class ShallowCopyExample {
public:
    int* data;

    ShallowCopyExample(int value) {
        data = new int(value);
    }

    ShallowCopyExample(const ShallowCopyExample& other) {
        data = other.data; // 浅いコピー
    }

    ~ShallowCopyExample() {
        delete data;
    }

    void show() {
        std::cout << "Data: " << *data << std::endl;
    }
};

浅いコピーの利点は、コピーが非常に高速であることです。しかし、元のオブジェクトとコピーしたオブジェクトが同じメモリを共有するため、片方のオブジェクトを変更するともう片方にも影響を与えます。また、どちらか一方が削除されると、もう一方が不正なメモリを参照してしまう可能性があります。

深いコピー

深いコピーは、オブジェクトのメンバー変数を新しいメモリ空間に再割り当てすることで、元のオブジェクトとは独立したコピーを作成する方法です。つまり、ポインタや参照が指す先も再コピーします。

class DeepCopyExample {
public:
    int* data;

    DeepCopyExample(int value) {
        data = new int(value);
    }

    DeepCopyExample(const DeepCopyExample& other) {
        data = new int(*other.data); // 深いコピー
    }

    ~DeepCopyExample() {
        delete data;
    }

    void show() {
        std::cout << "Data: " << *data << std::endl;
    }
};

深いコピーの利点は、コピーしたオブジェクトが完全に独立しているため、元のオブジェクトとコピーしたオブジェクトが互いに影響を及ぼさないことです。しかし、コピーの際に新しいメモリを割り当てるため、浅いコピーよりも処理が重くなります。

プロトタイプパターンでの使い分け

プロトタイプパターンを使用する際、浅いコピーと深いコピーのどちらを選択するかは、オブジェクトの使用状況に依存します。

  • 浅いコピーが適している場合: コピー元とコピー先が同じデータを共有しても問題がなく、コピーの速度が重要な場合。
  • 深いコピーが適している場合: コピー元とコピー先が独立して動作する必要があり、メモリの安全性やデータの整合性が重要な場合。

実装例の比較

以下に、浅いコピーと深いコピーの例を比較します。

int main() {
    // 浅いコピーの例
    ShallowCopyExample shallowOriginal(10);
    ShallowCopyExample shallowCopy = shallowOriginal;
    shallowOriginal.show(); // Output: Data: 10
    shallowCopy.show();     // Output: Data: 10
    // shallowOriginal.dataとshallowCopy.dataは同じアドレスを指している

    // 深いコピーの例
    DeepCopyExample deepOriginal(20);
    DeepCopyExample deepCopy = deepOriginal;
    deepOriginal.show();    // Output: Data: 20
    deepCopy.show();        // Output: Data: 20
    // deepOriginal.dataとdeepCopy.dataは異なるアドレスを指している

    return 0;
}

このように、プロトタイプパターンにおいては、オブジェクトの特性や用途に応じて浅いコピーと深いコピーを使い分けることが重要です。次に、プロトタイプパターンを実装する際の注意点について説明します。

実装における注意点

プロトタイプパターンを実装する際には、いくつかの重要な注意点があります。これらの点に留意することで、効率的かつ安全なコードを作成することができます。

コピーの正確性

クローンメソッドの実装において、オブジェクトの全てのメンバーが正確にコピーされることを確認する必要があります。特に、ポインタや動的に割り当てられたメモリを持つオブジェクトの場合、浅いコピーと深いコピーの違いを意識し、適切に実装することが重要です。

リソースの管理

動的にメモリを割り当てるオブジェクトの場合、メモリリークを防ぐために適切なリソース管理が必要です。コピーコンストラクタやデストラクタを正しく実装し、メモリが適切に解放されるようにします。

class ResourceManagementExample {
public:
    int* data;

    ResourceManagementExample(int value) {
        data = new int(value);
    }

    // コピーコンストラクタ
    ResourceManagementExample(const ResourceManagementExample& other) {
        data = new int(*other.data);
    }

    // デストラクタ
    ~ResourceManagementExample() {
        delete data;
    }
};

パフォーマンスの考慮

深いコピーは正確ですが、パフォーマンスに影響を与える可能性があります。特に、大量のデータや複雑なオブジェクトをコピーする場合、コピー操作がパフォーマンスのボトルネックになることがあります。必要に応じて、浅いコピーやコピーオンライト(Copy-On-Write)技法を検討することも有効です。

例外安全性

クローンメソッドやコピーコンストラクタ内で例外が発生した場合に備えて、例外安全性を確保することが重要です。例外が発生してもシステムが不整合状態にならないように、リソースの管理を慎重に行います。

不変オブジェクトの利用

オブジェクトの状態を変更しない場合、不変オブジェクト(Immutable Objects)を利用することで、コピーの必要性を減らすことができます。不変オブジェクトはスレッドセーフであり、複数のコピーが不要になるため、パフォーマンス向上にも寄与します。

抽象クラスとインターフェースの設計

プロトタイプパターンでは、抽象クラスやインターフェースを使用してクローンメソッドを定義します。これにより、具体的な実装クラスが異なる場合でも統一された方法でオブジェクトのクローンを作成できます。

class Shape {
public:
    virtual ~Shape() {}
    virtual Shape* clone() const = 0;
    virtual void draw() const = 0;
};

テストと検証

プロトタイプパターンを実装した後は、徹底的なテストと検証を行い、クローンメソッドが正しく動作することを確認します。特に、複製されたオブジェクトが期待通りに動作するかを確認するためのユニットテストを作成します。

void testCloneMethod() {
    Circle originalCircle(10);
    Shape* clonedCircle = originalCircle.clone();
    originalCircle.draw();  // Output: Circle with radius: 10
    clonedCircle->draw();   // Output: Circle with radius: 10

    delete clonedCircle;
}

以上の注意点を踏まえてプロトタイプパターンを実装することで、安全で効率的なオブジェクト複製が可能になります。次に、具体的な実装コードの詳細について見ていきましょう。

実装例のコード解説

ここでは、プロトタイプパターンの具体的な実装コードを紹介し、その詳細を解説します。これにより、プロトタイプパターンがどのように動作し、どのように利用できるかを理解します。

抽象クラス:Shape

まず、プロトタイプパターンの基礎となる抽象クラス Shape を定義します。このクラスには、クローンメソッドと描画メソッドが含まれています。

class Shape {
public:
    virtual ~Shape() {}
    virtual Shape* clone() const = 0;
    virtual void draw() const = 0;
};

具体クラス:Circle

次に、具体的な図形クラス Circle を定義します。このクラスは、円の半径をメンバー変数として持ち、clone() メソッドと draw() メソッドを実装します。

class Circle : public Shape {
private:
    int radius;
public:
    Circle(int r) : radius(r) {}

    // コピーコンストラクタ
    Circle(const Circle& other) : radius(other.radius) {}

    Shape* clone() const override {
        return new Circle(*this);
    }

    void draw() const override {
        std::cout << "Circle with radius: " << radius << std::endl;
    }
};

具体クラス:Rectangle

同様に、矩形クラス Rectangle を定義します。このクラスは、幅と高さをメンバー変数として持ち、clone() メソッドと draw() メソッドを実装します。

class Rectangle : public Shape {
private:
    int width, height;
public:
    Rectangle(int w, int h) : width(w), height(h) {}

    // コピーコンストラクタ
    Rectangle(const Rectangle& other) : width(other.width), height(other.height) {}

    Shape* clone() const override {
        return new Rectangle(*this);
    }

    void draw() const override {
        std::cout << "Rectangle with width: " << width << ", height: " << height << std::endl;
    }
};

クローンメソッドの利用

以下に、クローンメソッドを利用して図形オブジェクトを複製し、それぞれを描画する例を示します。

int main() {
    // オリジナル図形オブジェクトの作成
    Shape* originalCircle = new Circle(10);
    Shape* originalRectangle = new Rectangle(5, 7);

    // クローン図形オブジェクトの作成
    Shape* cloneCircle = originalCircle->clone();
    Shape* cloneRectangle = originalRectangle->clone();

    // オリジナルとクローンの描画を確認
    std::cout << "Original objects:" << std::endl;
    originalCircle->draw();    // Output: Circle with radius: 10
    originalRectangle->draw(); // Output: Rectangle with width: 5, height: 7

    std::cout << "Cloned objects:" << std::endl;
    cloneCircle->draw();       // Output: Circle with radius: 10
    cloneRectangle->draw();    // Output: Rectangle with width: 5, height: 7

    // メモリ解放
    delete originalCircle;
    delete originalRectangle;
    delete cloneCircle;
    delete cloneRectangle;

    return 0;
}

コードの解説

この実装例では、まず Shape 抽象クラスを定義し、具体的なクラスとして CircleRectangle を実装しました。各具体クラスには、clone() メソッドが実装されており、自身のコピーを作成して返すようになっています。

main() 関数では、オリジナルの図形オブジェクトを作成し、それらをクローンして複製します。オリジナルとクローンの両方を描画することで、正しく複製が行われたことを確認します。また、最後にメモリを適切に解放することも重要です。

この例を通じて、プロトタイプパターンの基本的な概念とその実装方法を理解できます。次に、プロトタイプパターンが使われる一般的なシナリオとその応用例について紹介します。

プロトタイプパターンの応用例

プロトタイプパターンは、さまざまなシナリオで効果的に使用されます。ここでは、プロトタイプパターンが実際にどのように応用されるかをいくつかの例で紹介します。

ゲーム開発におけるオブジェクトの生成

ゲーム開発では、多くの同一タイプのオブジェクト(例えば、敵キャラクターやアイテム)を頻繁に生成する必要があります。プロトタイプパターンを使用することで、これらのオブジェクトの初期化コストを削減し、効率的に複製することができます。

class Enemy : public Prototype {
private:
    int health;
    int attackPower;
public:
    Enemy(int h, int ap) : health(h), attackPower(ap) {}

    Enemy(const Enemy& other) : health(other.health), attackPower(other.attackPower) {}

    Prototype* clone() const override {
        return new Enemy(*this);
    }

    void display() const {
        std::cout << "Enemy with health: " << health << " and attack power: " << attackPower << std::endl;
    }
};

int main() {
    // オリジナルの敵オブジェクト
    Enemy* originalEnemy = new Enemy(100, 50);

    // クローンした敵オブジェクト
    Prototype* cloneEnemy = originalEnemy->clone();

    // 表示
    originalEnemy->display(); // Output: Enemy with health: 100 and attack power: 50
    cloneEnemy->display();    // Output: Enemy with health: 100 and attack power: 50

    // メモリ解放
    delete originalEnemy;
    delete cloneEnemy;

    return 0;
}

GUIアプリケーションでのウィジェットの複製

GUIアプリケーションでは、同じタイプのウィジェットを複数作成する必要があります。プロトタイプパターンを使用することで、既存のウィジェットを簡単に複製し、ユーザーインターフェースを効率的に構築できます。

class Widget : public Prototype {
private:
    std::string type;
    int width, height;
public:
    Widget(std::string t, int w, int h) : type(t), width(w), height(h) {}

    Widget(const Widget& other) : type(other.type), width(other.width), height(other.height) {}

    Prototype* clone() const override {
        return new Widget(*this);
    }

    void show() const {
        std::cout << "Widget of type: " << type << ", width: " << width << ", height: " << height << std::endl;
    }
};

int main() {
    // オリジナルのウィジェット
    Widget* originalWidget = new Widget("Button", 100, 30);

    // クローンしたウィジェット
    Prototype* cloneWidget = originalWidget->clone();

    // 表示
    originalWidget->show(); // Output: Widget of type: Button, width: 100, height: 30
    cloneWidget->show();    // Output: Widget of type: Button, width: 100, height: 30

    // メモリ解放
    delete originalWidget;
    delete cloneWidget;

    return 0;
}

データベースのレコードの複製

データベースアプリケーションでは、特定のレコードを複製して新しいレコードを作成する場合があります。プロトタイプパターンを使用することで、既存のレコードを簡単に複製し、新しいレコードとして挿入することができます。

class Record : public Prototype {
private:
    int id;
    std::string data;
public:
    Record(int i, std::string d) : id(i), data(d) {}

    Record(const Record& other) : id(other.id), data(other.data) {}

    Prototype* clone() const override {
        return new Record(*this);
    }

    void display() const {
        std::cout << "Record with id: " << id << ", data: " << data << std::endl;
    }
};

int main() {
    // オリジナルのレコード
    Record* originalRecord = new Record(1, "Sample Data");

    // クローンしたレコード
    Prototype* cloneRecord = originalRecord->clone();

    // 表示
    originalRecord->display(); // Output: Record with id: 1, data: Sample Data
    cloneRecord->display();    // Output: Record with id: 1, data: Sample Data

    // メモリ解放
    delete originalRecord;
    delete cloneRecord;

    return 0;
}

これらの例から分かるように、プロトタイプパターンはさまざまなシナリオで非常に有用です。次に、プロトタイプパターンの理解を深めるための演習問題を紹介します。

演習問題

プロトタイプパターンの理解を深めるために、以下の演習問題を解いてみてください。これらの問題を通じて、プロトタイプパターンの実装方法とその応用について実践的に学ぶことができます。

問題1:動物クラスの実装

動物を表す抽象クラス Animal を作成し、それを継承する Dog クラスと Cat クラスを実装してください。それぞれのクラスに clone() メソッドを実装し、動物オブジェクトを複製できるようにしてください。

class Animal {
public:
    virtual ~Animal() {}
    virtual Animal* clone() const = 0;
    virtual void makeSound() const = 0;
};

class Dog : public Animal {
private:
    std::string name;
public:
    Dog(std::string n) : name(n) {}
    Dog(const Dog& other) : name(other.name) {}

    Animal* clone() const override {
        return new Dog(*this);
    }

    void makeSound() const override {
        std::cout << name << " says Woof!" << std::endl;
    }
};

class Cat : public Animal {
private:
    std::string name;
public:
    Cat(std::string n) : name(n) {}
    Cat(const Cat& other) : name(other.name) {}

    Animal* clone() const override {
        return new Cat(*this);
    }

    void makeSound() const override {
        std::cout << name << " says Meow!" << std::endl;
    }
};

問題2:図書館の本のクラス

図書館の本を表すクラス Book を作成し、そのクローンメソッドを実装してください。本のタイトル、著者、ISBN番号をメンバー変数として持ち、それらをクローンできるようにしてください。

class Book {
private:
    std::string title;
    std::string author;
    std::string isbn;
public:
    Book(std::string t, std::string a, std::string i) : title(t), author(a), isbn(i) {}
    Book(const Book& other) : title(other.title), author(other.author), isbn(other.isbn) {}

    Book* clone() const {
        return new Book(*this);
    }

    void showDetails() const {
        std::cout << "Title: " << title << ", Author: " << author << ", ISBN: " << isbn << std::endl;
    }
};

問題3:プロトタイプレジストリの作成

プロトタイプレジストリを作成し、プロトタイプオブジェクトを登録、複製する機能を実装してください。このレジストリは、文字列キーとプロトタイプオブジェクトのマッピングを保持し、キーを使ってプロトタイプオブジェクトを取得、複製します。

#include <unordered_map>
#include <string>

class PrototypeRegistry {
private:
    std::unordered_map<std::string, Prototype*> registry;
public:
    void registerPrototype(const std::string& key, Prototype* prototype) {
        registry[key] = prototype;
    }

    Prototype* createClone(const std::string& key) {
        if (registry.find(key) != registry.end()) {
            return registry[key]->clone();
        }
        return nullptr;
    }

    ~PrototypeRegistry() {
        for (auto& entry : registry) {
            delete entry.second;
        }
    }
};

int main() {
    PrototypeRegistry registry;

    // プロトタイプの登録
    registry.registerPrototype("circle", new Circle(15));
    registry.registerPrototype("rectangle", new Rectangle(8, 10));

    // クローンの作成
    Shape* clonedCircle = registry.createClone("circle");
    Shape* clonedRectangle = registry.createClone("rectangle");

    // クローンの表示
    if (clonedCircle) clonedCircle->draw();       // Output: Circle with radius: 15
    if (clonedRectangle) clonedRectangle->draw(); // Output: Rectangle with width: 8, height: 10

    // メモリ解放
    delete clonedCircle;
    delete clonedRectangle;

    return 0;
}

これらの演習問題を解くことで、プロトタイプパターンの実装方法とその利点についての理解を深めることができるでしょう。最後に、プロトタイプパターンのまとめとその重要性について再確認します。

まとめ

プロトタイプパターンは、既存のオブジェクトをコピーして新しいオブジェクトを生成するデザインパターンです。このパターンは、オブジェクトの生成コストを削減し、効率的な複製を可能にするため、特に複雑なオブジェクトの作成において非常に有用です。浅いコピーと深いコピーの違いを理解し、適切に実装することで、メモリ管理やパフォーマンスの最適化を図ることができます。また、ゲーム開発、GUIアプリケーション、データベース管理など、さまざまなシナリオでプロトタイプパターンが応用されています。

プロトタイプパターンを理解し、実践的に応用することで、柔軟で再利用可能なコードを書くことができるようになります。今回の解説と演習を通じて、プロトタイプパターンの基本概念と実装方法を学びました。これを基に、さらに複雑なシステムにもプロトタイプパターンを適用してみてください。

コメント

コメントする

目次