ストリームにオブジェクトを置く、オブジェクトファクトリパターン

こんにちは、読者。 私の最も頻繁に使用されるパターンの1つ-オブジェクトのファクトリー、別の名前もこのパターンに適している-仮想コンストラクターに関する私の知識を共有したいと思います。



このパターンはどのようなものですか?



名前に基づいて、これがオブジェクトを作成する特定の特定のファクトリー(またはファクトリー)であると簡単に推測できます。 もう一度、ある言葉を言います。 実生活と同様に、工場には特定の専門性があり、特定のタイプの商品やデバイスを作成しています。 また、たとえば家具を製造する工場では、たとえばスマートフォン用のコンポーネントも製造できません。 プログラミングと同様に、オブジェクトファクトリは、単一のインターフェイスを使用する特定のタイプのオブジェクトのみを作成できます。 C ++でのこのパターンの主な利点は、単一のインターフェイスを使用してさまざまなクラスのオブジェクトを作成する単純化です。 多くの場合、プログラマーによって開発されたライブラリは、特定のオブジェクトを操作できるだけでなく、作成する必要もあります。 最も明らかな例は、さまざまな形式のファイルをダウンロードすることです。 どのファイルを事前にアップロードするかはわかりませんが、可能な形式のリストがあります。 ユーザーがファイルを指定すると、ライブラリはファイルの種類を判別し、そのファイルに適したローダーを呼び出します。 ほとんどの場合、プログラマはswitchなどの構造を使用して、インスタンス化する必要があるクラスを決定します。 そして、より多くの選択肢が表示されるほど、このデザインは大きくなり、後にlaterいモンスターになります。



オブジェクトのファクトリは何を提供できますか?

最初に、オブジェクトを作成するための簡単な方法で、最初の行のswitch / ifを減らします。

第二に、ファクトリオブジェクトを操作するための便利な方法。 特定のクラスが登録されているかどうか、登録されているクラスの数、およびクラスインスタンスをファクトリに追加および削除するための便利なメソッドを常に確実に知ることができます。 ファクトリを使用すると、何らかの構成を使用して作成する可能性のあるクラスのセットを制限できます。





特定の工場の作成



まず、テンプレートの使用方法を理解するために、テンプレートを使用せずに特定のファクトリーを実装します。

したがって、単一のインターフェイスをサポートするオブジェクトを作成できるクラスが必要です。 このインターフェイスをFoo、ファクトリーとそれぞれFooFactoryと呼びます。 オブジェクトを作成するための識別子として、文字列を取ります。



lass FooFactory { public: FooFactory(); ~virtual FooFactory(); Foo * create(const std::string & id) const; };
      
      





これまでのところ、これまでのところ非常に優れていますが、わずか数行のコードしか記述されていません。 しかし、どのようにしてクラスを追加し、実際にインスタンスを作成するのでしょうか? 問題を順番に分析しましょう。 オブジェクトを作成する前に、クラスをファクトリに追加する必要があります。 明らかに、何らかの機能が必要です。 しかし、オブジェクトではなくクラスを関数に渡す方法は?



または、クローニング戦略を使用できます。 つまり、オブジェクトを作成してファクトリーに配置し、ファクトリーメソッドcreateを呼び出すときにclone()タイプの関数を呼び出します。 率直に言って、このオプションは悪いので、プログラマーはFooインターフェースにクローン関数を追加し、それをすべての具象クラスに実装する必要があります。 さらに、オブジェクトをファクトリに追加するときは、オブジェクトを作成する必要があります。つまり、オブジェクトにメモリを割り当てます。 また、オブジェクトは非常に重い場合があります。 はい、もちろん、現代の世界ではメモリ/パフォーマンスの問題はそれほど深刻ではありませんが、C ++は高レベルと低レベルの両方の構造を最適化できる言語なので、使用しないでください。



別のオプションは、クラスをファクトリーに追加するときに、目的のタイプのオブジェクトを作成するメソッドの関数にポインターを渡します。 この場合、以前のバージョンよりもさらに多くの問題があります。 作成された具体的なクラスごとにこの関数を実装する必要があり、クラスをファクトリに追加するときに、この関数をパラメーターとして渡します。 もちろん、マクロを作成することはできますが、C ++のマクロは悪であると多くの人が考えているという事実は言うまでもなく、見た目はあまり美しくありません。



しかし一方で、C ++には、この困難なタスクを解決するのに役立つテンプレートがあります。 タイプFooのオブジェクトを作成するには、必要なクラスのオブジェクトを作成する役割を担う他のオブジェクト、補助オブジェクトを使用します。 一般の人々では、そのようなクラスは作成者またはインスタンス生成者と呼ばれます。 抽象クリエータークラスが作成されます。通常、メソッドは1つだけです。特定のインターフェイスのオブジェクト、現在のバージョンでは、Foo型のオブジェクトを作成します。 それに基づいて、クリエーターの具体的なクラスはすでに作成されています。 抽象クリエータークラスのおかげで、特定のクリエーターのセットを任意のコンテナーに格納できます。 この場合、テンプレートはマクロの役割を果たし、テンプレートパラメーターに基づいて特定の作成者用のコードを生成できます。



Foo型のオブジェクトを作成するための抽象クラス:



 class abstractFooCreator { public: virtual fooCreator() {} virtual Foo * create() const = 0; };
      
      





特定のクラスのオブジェクトを作成するために実際のコードが生成されるテンプレートクラス:



 template <class C> class fooCreator : public abstractFooCreator { public: virtual Foo * create() const { return new C(); } };
      
      





したがって、クラスをファクトリに追加するためのテンプレートメソッドを既に記述できます。 必要なのは、作成者を保存するコンテナを選択することだけです。 明白な選択はstd :: mapです。



 typedef std::map<std::string, abstractFooCreator*> FactoryMap; FactoryMap _factory; template <class C> void add(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = new fooCreator<C>(); }
      
      





これで、Fooインターフェースをサポートするクラスを追加できるファクトリーの最初の作業バージョンができました。

実際、2番目の問題、つまり目的のタイプのオブジェクトの作成は、実際には作成者の格納方法にのみ依存していたため、ほとんど解決されました。



 void Foo * create(const std::string & id) const { typename FactoryMap::iterator it = _factory.find(id); if (it != _factory.end()) return it->second->create(); return 0; }
      
      







すべてのコードを収集して、全体像を見てみましょう。



展開する
 class abstractFooCreator { public: virtual fooCreator() {} virtual Foo * create() const = 0; }; template <class C> class fooCreator : public abstractFooCreator { public: virtual Foo * create() const { return new C(); } }; lass FooFactory { protected: typedef std::map<std::string, abstractFooCreator*> FactoryMap; FactoryMap _factory; public: FooFactory(); ~virtual FooFactory(); template <class C> void add(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = new fooCreator<C>(); } Foo * create(const std::string & id) { typename FactoryMap::iterator it = _factory.find(id); if (it != _factory.end()) return it->second->create(); return 0; } };
      
      









さて、このファクトリの使用方法の小さな例です。 ファクトリーへのクラスの追加:



 FooFactory factory; factory.add<MyFoo>("MyFoo"); factory.add<MyFoo2>("MyFoo2"); factory.add<ImprovedFoo>("ImprovedFoo");
      
      





ファクトリを使用してオブジェクトを作成する:



 Foo * p = factory.create("MyFoo2");
      
      





ファクトリーの助けを借りればとても簡単で、単一のインターフェースをサポートするオブジェクトの作成を制御できます。



テンプレートファクトリの作成



それでは、この特定の例に基づいて、非常に具体的なパターンを作成しましょう。



私たちの能力を満たすために工場は何を必要としますか もちろん、これは識別子タイプのジョブであり、列挙、std ::文字列、または識別子に適した他のタイプのいずれかです。 2番目は、作成するオブジェクトの実際のタイプです。上記の例では、Fooクラスがその役割を果たしました。



 template <class Base, class IdType> class ObjectFactory { public: ObjectFactory() {} virtual ~ObjectFactory(); template <class C> void add(const IdType & id); Base * create() const; };
      
      





これがテンプレートファクトリの外観です。

その実装に進みましょう。 Fooの例のように、特定のタイプのオブジェクトを作成する問題を解決する必要があります。 つまり、Base型のオブジェクトを作成するメソッドを持つ抽象クラスと、この抽象クラスを継承し、テンプレートパラメーターを使用してこのメ​​ソッドを実装する具体的な作成者クラスが必要です。



 template <class Base> class AbstractCreator { public: AbstractCreator() {} virtual ~AbstractCreator(){} virtual Base* create() const = 0; }; template <class C, class Base> class Creator : public AbstractCreator<Base> { public: Creator() { } virtual ~Creator() {} Base * create() const { return new C(); } };
      
      





さて、今、私たちの工場でこれらのクラスを使用する必要があります。



 template <class Base, class IdType> class ObjectFactory { protected: typedef AbstractCreator<Base> AbstractFactory; typedef std::map<IdType, AbstractFactory*> FactoryMap; FactoryMap _factory; public: ObjectFactory() {} virtual ~ObjectFactory(); template <class C> void add(const IdType & id) { registerClass(id, new Creator<C, Base>()); } protected: void registerClass(const IdType & id, AbstractFactory * p) { typename FactoryMap::iterator it = _factory.find(id); if (it == _factory.end()) _factory[id] = p; else delete p; } };
      
      





Fooの例と比較して何が追加されましたか? もちろん、これはregisterClassメソッドで、AbstractFactory型のオブジェクトをパラメーターとして受け取り、addメソッドのテンプレートパラメーターとして指定するクラスのオブジェクトを作成できます。



工場出荷時の動作を設定する機能を追加します



現時点では、ファクトリ内の目的のクラスの存在、それらの番号の確認、削除など、残りの単純なメソッドの作成は省略します。最後に完全なリストを確認できます。 工場をさらに柔軟にすることをお勧めします。 実際、ファクトリーの識別子タイプと基本クラス、ファクトリーに配置されるクラスが実装するインターフェイスを設定することに加えて、特定のエラーが発生した場合のファクトリーの動作の設定を追加することもできます。 要求されたタイプが作成時に工場にない場合はどうなりますか? ゼロを返すか、例外をスローしますか? 存在しないクラスを削除しようとした場合、または既に登録されているオブジェクトをファクトリーに追加しようとした場合、どうすればよいですか? 工場の動作を変更できるようにするには、別の設計パターンを使用する必要があります。これは、政治家としても知られる戦略です。 このパターンは、A。Alexandrescu著「C ++のモダンデザイン」で非常によくカバーされています。



私がサポートしているプロジェクトでは、当面は2つの動作しかありません。 エラーに対する反応の欠如、および私のプロジェクトで使用されている特別なタイプの例外をスローします。 デバッグおよび一般的なエラーの詳細な説明に必要な唯一のパラメーターは、作成、削除などの関数に渡す識別子です。 したがって、ポリシーのインターフェースは次のようになります。



 template <class Base, class Type> class ObjectFactoryIgnoreErrorPolicy { public: Base * onCreateFailed(const Type & type) const { return 0; } void onRemoveFailed(const Type & type) {} void onDuplicateRegistered(const Type & type) {} };
      
      







動作自体を実装する実際の機能に加えて、ポリシークラスには、ファクトリが実装するインターフェイスタイプと、エラーが発生し、すべてのメソッドに渡される識別子タイプも必要です。 IgnoreErrorPolicyとの類推により、エラーの場合に例外をスローするポリシーの例も示します。



実装例
 class ObjectFactoryException : public std::exception { std::string _msg; public: ObjectFactoryException(const std::string & msg) throw() : _msg(msg) {} virtual ~ObjectFactoryException() throw() {} virtual const char * what() const throw() { return _msg.c_str(); } }; template <class Base, class Type> class ObjectFactoryThrowExceptionErrorPolicy { public: std::string generateMessage(const char * msg, const Type & type) const { std::stringstream strm; strm << msg << ", requested type id : " << type; return strm.str(); } Base * onCreateFailed(const Type & type) const { throw ObjectFactoryException(generateMessage("ObjectFactory - can't create object (not registered)", type)); } void onRemoveFailed(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - can't remove class (not registered)", type)); } void onDuplicateRegistered(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - class already registered", type)); } };
      
      







次に、ポリシーを工場に組み込みます。 これを行うにはいくつかの方法があります-ポリシークラスから単純に継承するか、ファクトリクラス属性としてポリシークラスを使用します。 どちらの場合でも、唯一の問題は、ファクトリクラスからポリシークラスのテンプレートパラメータを設定する方法です。 C ++の注目すべき機能は、テンプレートテンプレートパラメータでこれを支援します。 ファクトリクラスを宣言するときに、クラスとしてだけでなくテンプレートクラスとしてパラメータとして渡すことを示します。 次のようになります。



 template <class Base, class IdType, template <class, class> class ObjectFactoryErrorPolicy = ObjectFactoryIgnoreErrorPolicy > class ObjectFactory : public ObjectFactoryErrorPolicy<Base, IdType> { …
      
      





3番目のパラメーターは、テンプレート<class、class> class ...

また、このパラメーターにはデフォルトのポリシーがすぐに設定されます。 別のポリシーを指定する必要がある場合は、ファクトリのtypedefを使用して、最後のパラメーターとして必要なポリシーを使用してクラス名を指定する必要があります。次に例を示します。



 typedef ObjectFactory<Foo, FooType, ObjectFactoryThrowExceptionPolicy> FooFactory;
      
      





私の場合、ポリシーを無視するエラーを頻繁に使用するため、デフォルトのパラメーターとして設定します。 さらに実行して、実行時にポリシーを動的に変更する機能を使用してポリシーを動的にすることもできますが、現時点ではこれは関係ないため、これまでの小さなプラクティスではこのメカニズムは必要ありませんでした。 実際、これがポリシークラスから継承する方法を選択し、ファクトリの属性として使用しない方法を選択した理由です。



リスティング





さて、今度は在庫を調べて、実装のパターンの完全なリストを提示します。

上場
 #include <map> #include <string> #include <memory> #include <sstream> namespace grs { /*  ,       */ template <class Base> class AbstractCreator { public: AbstractCreator() { } virtual ~AbstractCreator() { } virtual Base* create() const = 0; private: AbstractCreator(const AbstractCreator&); AbstractCreator& operator = (const AbstractCreator&); }; template <class C, class Base> class Creator : public AbstractCreator<Base> { public: Creator() { } virtual ~Creator() { } Base * create() const { return new C(); } }; /*    */ class ObjectFactoryException : public std::exception { std::string _msg; public: ObjectFactoryException(const std::string & msg) throw() : _msg(msg) {} virtual ~ObjectFactoryException() throw() {} virtual const char * what() const throw() { return _msg.c_str(); } }; template <class Base, class Type> class ObjectFactoryIgnoreErrorPolicy { public: Base * onCreateFailed(const Type & type) const { return 0; } void onRemoveFailed(const Type & type) { } void onDuplicateRegistered(const Type & type) { } }; template <class Base, class Type> class ObjectFactoryThrowExceptionErrorPolicy { public: std::string generateMessage(const char * msg, const Type & type) const { std::stringstream strm; strm << msg << ", requested type id : " << type; return strm.str(); } Base * onCreateFailed(const Type & type) const { throw ObjectFactoryException(generateMessage("ObjectFactory - can't create object (not registered)", type)); } void onRemoveFailed(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - can't remove class (not registered)", type)); } void onDuplicateRegistered(const Type & type) { throw ObjectFactoryException(generateMessage("ObjectFactory - class already registered", type)); } }; /*  */ template <class Base, class IdType = int, template <class, class> class ObjectFactoryErrorPolicy = ObjectFactoryIgnoreErrorPolicy > class ObjectFactory : public ObjectFactoryErrorPolicy<Base, IdType> { protected: typedef AbstractCreator<Base> AbstractFactory; typedef std::map<IdType, AbstractFactory*> FactoryMap; public: ObjectFactory() { } virtual ~ObjectFactory() { for (typename FactoryMap::iterator it = _map.begin(), endIt = _map.end(); it != endIt; ++it) delete it->second; } Base * create(const IdType & id) const { typename FactoryMap::const_iterator it = _map.find(id); if (it != _map.end()) return it->second->create(); return onCreateFailed(id); } template <class C> void add(const IdType & id) { registerClass(id, new Creator<C, Base>); } void remove(const IdType & id) { typename FactoryMap::iterator it = _map.find(id); if (it != _map.end()) { delete it->second; _map.erase(it); } else onRemoveFailed(id); } bool isRegistered(const IdType & id) const { return _map.find(id) != _map.end(); } size_t size() const { return _map.size(); } protected: void registerClass(const IdType & id, AbstractFactory * pAbstractFactory) { std::auto_ptr<AbstractFactory> ptr(pAbstractFactory); typename FactoryMap::iterator it = _map.find(id); if (it == _map.end()) _map[id] = ptr.release(); else onDuplicateRegistered(id); } private: ObjectFactory(const ObjectFactory&); ObjectFactory& operator = (const ObjectFactory&); FactoryMap _map; }; } // end of grs namespace
      
      









工場を使用する小さな例
 #include "ObjectFactory.h" #include <iostream> enum Type { fooType, barType, maskedType, unknownType, firstType = fooType, lastType = maskedType, }; std::ostream & operator << (std::ostream & strm, const Type & type) { const char * names[] = {"foo", "bar", "masked"}; if (type < firstType || type > lastType) return strm << "unknown type(" << int(type) << ")"; return strm << names[type]; }; class Base { public: virtual ~Base() {} virtual Type type() const = 0; }; class Foo : public Base { public: virtual Type type() const { return fooType; } }; class Bar : public Foo { public: virtual Type type() const { return barType; } }; class MaskedFoo : public Foo { public: virtual Type type() const { return barType; } }; typedef grs::ObjectFactory<Base, Type> TypeFactory; void checkType(TypeFactory & factory, Type type) { std::auto_ptr<Base> p; p.reset(factory.create(type)); std::cout << "Object with type : " << type; if (p.get()) { if (type == p->type()) std::cout << " - successfully created\n"; else std::cout << " - created, but type mismatch\n"; } else std::cout << " - create failed\n"; } int main() { TypeFactory factory; factory.add<Foo>(fooType); factory.add<Bar>(barType); factory.add<MaskedFoo>(maskedType); checkType(factory, fooType); checkType(factory, barType); checkType(factory, maskedType); checkType(factory, unknownType); return 0; }
      
      









また、 bitbucketを使用してパターンをパターンとともに取得することもできますが、他にも便利なクラスがいくつかあります。



それだけです 優れた創造的なコーディング!



参照資料



デザインパターンに興味がある場合は、これらの本に注意することをお勧めします。 A. Alexandrescuの本では、テンプレートを操作するときのC ++構文の機能が非常によく伝えられています。 4人のギャングの本では、多くのプログラミング言語で使用されている最も人気のあるパターンをすべて見つけることができます。




All Articles