Factory

since: 2002-06-22 update: 2002-06-22 count:

非常に簡単です。単なる ID とその生成法を登録する map です。 まあ、独自の方法でシリアライズされた文字列から オブジェクトを引っ張り出したりするんでしょうね。

class A {
public:
    virtual void print() =0;
    virtual ~A() {}
};
class B : public A {
public:
    virtual void print() { std::cout << "B" << std::endl; }
};
class C : public A {
public:
    virtual void print() { std::cout << "C" << std::endl; }
};

こんなクラス群から、B, C が作りたいのです。とても良くある状況ですな。 準備は…

const int B_CODE = 0;
const int C_CODE = 1;

template <class _New>
A* newA() { return new _New; }

typedef Loki::Factory<A, int> AFactory;

って感じ。B, C の為の ID と、B, C を作成するための関数を準備。 今回は template を使って作成関数は一つにまとめました。 後はお約束のポリシーを設定しての typedef。 今回は A を返しますよ、ID には int を使ってますよ、と Factory に教える。

んで、使いかたはというと、まずクラスを登録して、

AFactory factory;
factory.Register(B_CODE, newA<B>);
factory.Register(C_CODE, newA<C>);

次に実際にオブジェクトを生成します。

std::vector<A*> vec;
vec.push_back(factory.CreateObject(B_CODE));
vec.push_back(factory.CreateObject(C_CODE));
vec.push_back(factory.CreateObject(B_CODE));

// should print "BCB"
std::for_each(vec.begin(), vec.end(), std::mem_fun(&A::print));

まあ、簡単ですね。ちゃんと "BCB" と出力されます。

サンプルコード

今回は簡単なので、何でこんなクラスが欲しいのかも簡単に説明します。 こういうクラスが無ければ、多分こういうコードを書きます。

#include "B.h"
#include "C.h"

A* makeA(int id) {
    switch (id) {
    case B_CODE:
        return new B;
    case C_CODE:
        return new C;
}

これがライブラリコードだった場合、 クライアントコード側で A を継承した D などを作って拡張できない、 っていうのが困るわけです。

作ってみると解りますが、一般に、 ユーザ拡張クラスの生成を伴うライブラリってのは、結構実装が厄介です。 これは他のメソッドと異なり、 コンストラクタが完全に静的であることによります。 こういったライブラリでは、 クライアントコードに対して何らかの制限を加えることになります。 今回の例では、クライアントコードで、 クラスごとに異なる ID を配り、それを登録しなければならない、 というのが制約となっています。

コードが煩雑になるので、ここでは使いませんでしたが、 こういったファクトリのオブジェクトは一つで十分であり、 登録を分散させることを考えるとどこからでもアクセスできるべきであるので、 Singleton パターンと組み合わせて使うことが推奨されています。


home / index

全てリンクフリーです。 コード片は自由に使用していただいて構いません。 その他のものはGPL扱いであればあらゆる使用に関して文句は言いません。 なにかあれば下記メールアドレスへ。

shinichiro.hamaji _at_ gmail.com / shinichiro.h