2009-07-03 9 views
1

同じ階層に異なるクラスを作成するためのファクトリ関数を検討しています。私は次のように通常の工場が正常に実装されていることを理解する:ファクトリパターンをコードの柔軟性と組み合わせるにはどうすればいいですか

Person* Person::Create(string type, ...) 
{ 
    // Student, Secretary and Professor are all derived classes of Person 
    if (type == "student") return new Student(...); 
    if (type == "secretary") return new Secretary(...); 
    if (type == "professor") return new Professor(...); 
    return NULL; 
} 

私は様々な条件がハードコードされている必要はありませんように、プロセスを自動化することができるように方法を考えるしようとしています。

これまでのところ私は考えることができる唯一の方法は、地図とプロトタイプパターンを使用している:

最初の要素を入力文字列と第二のクラスのインスタンス(試作品)を保持しますマップ:

std::map<string, Person> PersonClassMap; 
// This may be do-able from a configuration file, I am not sure 
PersonClassMap.insert(make_pair("student", Student(...))); 
PersonClassMap.insert(make_pair("secondary", Secretary(...))); 
PersonClassMap.insert(make_pair("professor", Professor(...))); 

関数は次のようになります。あなたが唯一の工場tで作成したクラスをしたい場合は

Person* Person::Create(string type) 
{ 
    map<string, Person>::iterator it = PersonClassMap.find(type) ; 
    if(it != PersonClassMap.end()) 
    { 
     return new Person(it->second); // Use copy constructor to create a new class instance from the prototype. 
    } 
} 

残念ながら、プロトタイプの方法でのみ動作します引数をサポートしていないので、毎回同じである。

いい方法でそれを行うことが可能かどうか誰かが知っていますか、または私は工場の機能についていますか?

+0

私はあなたがどこかそうでなければ、その人を削除願って、間いくつかのメモリが漏れています;) – Partial

答えて

0

私はC++に慣れていませんが、多くのlangugaesではデリゲートやクロージャの概念があります。インスタンスにマッピングする代わりに、オブジェクトの作成を担当する関数(delegate、closure)にマップすることを意味します。

+0

C++にはクロージャや何もリモートにありませんar。メソッドポインタはありますが、ソリューションに何も追加しません。 – PanCrit

+0

@PanCrit、あなたは冗談を言っていますか?抽象的な工場パターンに慣れていますか?単一のメソッドを持つファクトリは、ネイティブの代理人を代用するのに適しています。そして、それはC + +でaw awailable。 –

-1

まあ、あなたがそれを行うにはより高速な方法をしたい場合は、次に列挙し、switchステートメントを使用すると、より高速/他の場合if文シーケンシャルに処理するよりも多くの時間となります...

+0

enumとswitchの方が速く動作するかもしれませんが、維持するのはずっと多くの作業です。メンテナンス可能なオブジェクトベースのソリューションがはるかに価値があると私は考えています。 – PanCrit

1

私は通常、ファクトリメソッドを構築する(またはクライアントが作成されるオブジェクトに関するいくつかの情報を提供するが、結果がどのような具体的なクラスであるかを知らないときは、ファクトリへのインターフェイスの表現方法の決定は、クライアントがどのような情報を持っているかによって決まります。文字列(例えば、解析されるプログラムテキスト)、またはパラメータ値のセット(n空間で幾何学的オブジェクトを作成する場合は次元数とサイズ)を提供することができます。次に、ファクトリメソッドは情報を調べ、作成するオブジェクトの種類や、より具体的なファクトリを呼び出すかどうかを決定します。

したがって、何を構築するかについての決定は、呼び出し元によって行われるべきではありません。彼女が知っていれば、工場の理由はありません。ビルドされるもののリストがオープンエンドである場合、特定の実装が、ファクトリメソッドがどのメソッドを呼び出すかを決定するための構築メソッドと識別子関数の両方を提供することを可能にする登録プロトコルを持つことさえあります。

どのような種類のオブジェクトをビルドするかを決定するためには、どの情報が必要で十分であるかによって大きく異なります。

-1

2つの実装を見ると、論理的には同じです。 最初の実装は、再帰的ループがアンロールされた場合の2番目の実装と同じです。したがって、実際には2番目の実装の利点はありません。

あなたが何をしても、あなたのコンストラクタにマップされている場所をリストする必要があります。役立つことができ、それを行うための

一つの方法は、あなたが、メモリには、このXMLファイルを読み、あなたのコンストラクタを取り戻すためにリフレクションを使用することができます別々のxmlファイルに

<person> 
    <type> student </type> 
    <constructor> Student </type> 
</person> 
.... 

をこのマップを持つことです。与えられた型に対して。あなたがC++を使っているとすれば、これはC++がリフレクションを標準で持っていないので単純ではありません。 C++でのリフレクションを提供するために拡張機能を探す必要があります。

ただし、これらのすべての代替案は、元の実装で行ったことをエスケープすることはできません。つまり、型からコンストラクタへのマッピングをリストし、マップを検索します。

+0

あなたのアプローチは何のためにもあまりにも複雑です... – Partial

+0

完全にポイントを逃す方法!まず第一に、私はOPのコメントの引用に返信していました。「これはコンフィギュレーションファイルからは可能かもしれませんが、わかりません」と私は彼にそれを行う方法を示しました。しかし、あなたが明らかに逃した私の要点は、これらのアプローチのどれもが彼の代替案を見つけようとしていた彼の最初の実装に沸騰するということでした! – hhafez

1

ファクトリメソッドを登録することができます(コピーする要素をprebuiltするのではなく)。これにより、具体的なファクトリに渡されるパラメータを持つ抽象ファクトリを呼び出すことができます。ここでの制限は、すべての具体的な工場のパラメータのセットが同じでなければならないということです。

typedef std::string discriminator; 
typedef Base* (*creator)(type1, type2, type3); // concrete factory, in this case a free function 
typedef std::map< discriminator, creator > concrete_map; 
class Factory // abstract 
{ 
public: 
    void register_factory(discriminator d, creator c) { 
     factories_[ d ] = c; 
    } 
    Base* create(discriminator d, type1 a1, type2 a2, type3 a3) 
    { 
     return (*(factories_[ d ]))(a1, a2, a3); 
    } 
private: 
    concrete_map factories_; 
}; 

私は、サンプルコードを減らすために無料の機能クリエーターを使用していたが、あなたはconcrete_factoryタイプを定義し、上記の「クリエーター」要素の代わりに使用することができます。ここでもわかるように、ファクトリの「作成」メソッドでは、引数の固定セットに制限されています。

各コンクリート工場は、与えられた型のコンストラクタに引数を渡すことができます。

Base* createDerived1(type1 a1, type2 a2, type3 a3) 
{ 
    return new Derived1(a1, a2, a3); 
} 

を使用すると、外部のオブジェクトへの参照を保持するインスタンスを作成することができますので、これはあなたのアプローチよりも柔軟性がある(これらにのみ初期化することができますより一般的な言葉遣いで建設後に異なる状態にリセットすることができない物体を含むことができる。

1

純粋な抽象度cloneメソッドをPersonクラスに追加します(これは主にサブクラス化のために存在する抽象クラスである必要があります)。具体的な "none above the above"の種類が必要な場合人物、それが最善ではなく、基本クラス自体)としてよりも、別の具象サブクラスOtherKindOfPersonを介して行われます:例えば、

virtual Person* clone() const = 0; 

、すべての具象サブクラスでそれをオーバーライドします学生には、特定の具体的なサブクラスのコピーctorのを呼び出すnewで:

std::map<string, Person*> PersonClassMap; 

[[あなたは無地よりも、いくつかのよりスマートポインタを使用することができます。

Person* clone() const { return new Student(*this); } 

またにレジストリマップを変更する必要があります古いPerson *ですが、マップとそのエントリのすべてがおそらくプロセスと同じくらい長く生き残る必要があるので、これは間違いなく大したことではありません。よりスマートなポインタから得られる主な付加価値は、ポインタ "! - )]]

今、あなたの工場の機能は、単純で終了することができます。

return it->second->clone(); 

変更は余分な属性を持っているサブクラスに基本クラスのコピーのctorを使用しての「スライス」効果を避けるだけでなく、維持するために必要とされています仮想メソッドの解像度。

具体的なクラスをサブクラス化して他の具象クラスを生成することは、悪い考えです。なぜならこれらの影響は難しく、バグの原因かもしれないからです(Haahrの勧告を参照してください:彼はJavaについて書いていますが、 C++やその他の言語[確かに私はC++での彼の勧告は、さらに重要な発見!]

0

あなたは人の各型の列挙型を作ることができる:。

enum PersonType { student, secretary, professor }; 
関連する問題