簡単なC ++オブジェクトファクトリを設計する方法は?

StackOverflow https://stackoverflow.com/questions/333400

  •  22-07-2019
  •  | 
  •  

質問

私のアプリケーションには、1回インスタンス化される10〜20のクラスがあります[*]。次に例を示します。

class SomeOtherManager;

class SomeManagerClass {
public:
    SomeManagerClass(SomeOtherManager*);
    virtual void someMethod1();
    virtual void someMethod2();
};

クラスのインスタンスは1つのオブジェクトに含まれています:

class TheManager {
public:
    virtual SomeManagerClass* someManagerClass() const;
    virtual SomeOtherManager* someOtherManager() const;
    /** More objects... up to 10-20 */
};

現在、TheManagerはオブジェクトを作成するために new 演算子を使用しています。

私の意図は、プラグインを使用して、SomeManagerClass(またはその他のクラス)の実装を別のものに置き換えることです。実装を置き換えるには、2つの手順が必要です。

  1. SomeManagerClass [プラグイン]を継承するクラスDerivedSomeManagerClassを定義します
  2. デフォルト(SomeManagerClass)[アプリケーション]の代わりに新しいクラス(DerivedSomeManagerClass)を作成します

何らかのオブジェクトファクトリが必要だと思いますが、作成するのは常に1つのタイプ(デフォルトの実装またはユーザー実装)のみであるため、かなり単純なはずです。

先ほど説明したような単純なファクトリーの設計方法についてのアイデアはありますか?将来さらに多くのクラスが存在する可能性があるという事実を考慮してください。したがって、簡単に拡張できるはずです。

[*]何度も発生するかどうかは気にしません。

編集: TheManagerには3つ以上のオブジェクトが含まれていることに注意してください。

役に立ちましたか?

解決

ここには2つの問題があると思います。

1つの問題は、TheManagerが作成する必要があるクラスを name する方法です。 「クラスを作成する方法」への何らかのポインタを保持する必要があります。考えられる解決策は次のとおりです。

  • クラスの種類ごとに個別のポインタを保持し、それを設定する方法がありますが、DRYの原則に違反するため、これは気に入らないと既に述べています
  • キーが列挙型または文字列であるようなテーブルを保持する。この場合、セッターはパラメーターを持つ単一の関数です(もちろん、キーが列挙型の場合、マップの代わりにベクトルを使用できます)

他の問題は、この「クラスを作成する方法」とは何ですか?残念ながら、コンストラクタへのポインタを直接保存することはできませんが、次のことができます。

  • 他の人が指摘しているように、各クラスのファクトリを作成します
  • 静的な「作成」を追加するだけです;各クラスの機能。それらが一貫した署名を保持している場合、関数へのポインタを使用できます

テンプレートは、どちらの場合でも不要なコードの重複を回避するのに役立ちます。

他のヒント

SomeManagerClassを継承するクラス(plugin1)を想定して、型を構築するにはクラス階層が必要です

class factory
{
public:
    virtual SomeManagerClass* create() = 0;
};

class plugin1_factory : public factory
{
public:
    SomeManagerClass* create() { return new plugin1(); }
};

これらのファクトリをstd :: mapに割り当てて、文字列にバインドできます

std::map<string, factory*>  factory_map;
...
factory_map["plugin1"] = new plugin1_factory();

最後に、TheManagerはプラグインの名前を(文字列として)知る必要があり、1行のコードでSomeManagerClass型のオブジェクトを返すことができます:

SomeManagerClass* obj = factory_map[plugin_name]->create();

編集:プラグインごとに1つのプラグインファクトリクラスを使用したくない場合は、これで以前のパターンを変更できます:

template <class plugin_type>
class plugin_factory : public factory
{
public:
   SomeManagerClass* create() { return new plugin_type(); }
};

factory_map["plugin1"] = new plugin_factory<plugin1>();

これははるかに優れたソリューションだと思います。さらに、costructorに文字列を渡すと、「plugin_factory」クラスは「factory_map」に自分自身を追加できます。

C ++ファクトリーに関する別のSO質問で回答しました。フレキシブルファクトリに関心がある場合は、そこをご覧ください。私は、ET ++からマクロを使用するための古い方法を説明しようとしていますが、これは私にとってはうまくいきました。

ET ++ は、古いMacAppをC ++およびX11に移植するプロジェクトでした。その努力の中で、エリック・ガンマなどはデザインパターン

について考え始めました

「ベース」を作成します;すべての基本的なマネージャーを作成するための仮想メソッドを持つファクトリーで、「メタマネージャー」 (質問のTheManager)は、コンストラクターパラメーターとして基本ファクトリーへのポインターを取得します。

「工場」がCXYZWManagerのインスタンスを派生させることでカスタマイズできますが、代わりにCXYZWManagerのコンストラクターは&quot; custom&quot;工場。

&quot; CSomeManager&quot;を出力する長いコード例および&quot; CDerivedFromSomeManager&quot;:

#include <iostream>
//--------------------------------------------------------------------------------
class CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CSomeManager";}
  };

//--------------------------------------------------------------------------------
class COtherManager
  {
  };

//--------------------------------------------------------------------------------
class TheManagerFactory
  {
  public:
    // Non-static, non-const to allow polymorphism-abuse
    virtual CSomeManager   *CreateSomeManager() { return new CSomeManager(); }
    virtual COtherManager  *CreateOtherManager() { return new COtherManager(); }
  };

//--------------------------------------------------------------------------------
class CDerivedFromSomeManager : public CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CDerivedFromSomeManager";}
  };

//--------------------------------------------------------------------------------
class TheCustomManagerFactory : public TheManagerFactory
  {
  public:
    virtual CDerivedFromSomeManager        *CreateSomeManager() { return new CDerivedFromSomeManager(); }

  };

//--------------------------------------------------------------------------------
class CMetaManager
  {
  public:
    CMetaManager(TheManagerFactory *ip_factory)
      : mp_some_manager(ip_factory->CreateSomeManager()),
        mp_other_manager(ip_factory->CreateOtherManager())
      {}

    CSomeManager  *GetSomeManager()  { return mp_some_manager; }
    COtherManager *GetOtherManager() { return mp_other_manager; }

  private:
    CSomeManager  *mp_some_manager;
    COtherManager *mp_other_manager;
  };

//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
  {
  TheManagerFactory standard_factory;
  TheCustomManagerFactory custom_factory;

  CMetaManager meta_manager_1(&standard_factory);
  CMetaManager meta_manager_2(&custom_factory);

  std::cout << meta_manager_1.GetSomeManager()->ShoutOut() << "\n";
  std::cout << meta_manager_2.GetSomeManager()->ShoutOut() << "\n";
  return 0;
  }

これは私が考えた解決策です。最良の解決策ではありませんが、より良い解決策を考えるのに役立つかもしれません:

クラスごとに作成者クラスがあります:

class SomeManagerClassCreator {
public:
    virtual SomeManagerClass* create(SomeOtherManager* someOtherManager) { 
        return new SomeManagerClass(someOtherManager); 
    }
};

その後、作成者は1つのクラスに集められます:

class SomeManagerClassCreator;
class SomeOtherManagerCreator;

class TheCreator {
public:
    void setSomeManagerClassCreator(SomeManagerClassCreator*);
    SomeManagerClassCreator* someManagerClassCreator() const;

    void setSomeOtherManagerCreator(SomeOtherManagerCreator*);
    SomeOtherManagerCreator* someOtherManagerCreator() const;
private:
    SomeManagerClassCreator* m_someManagerClassCreator;
    SomeOtherManagerCreator* m_someOtherManagerCreator;
};

そしてTheManagerは、内部作成のためにTheCreatorで作成されます:

class TheManager {
public:
    TheManager(TheCreator*);
    /* Rest of code from above */
};

このソリューションの問題は、DRYに違反することです。クラス作成者ごとに、TheCreatorでsetter / getterを作成する必要があります。

これは、Abstract Factoryパターンとは対照的に、関数テンプレートを使用する方がはるかに簡単に思えます

class ManagerFactory
{
public:
    template <typename T> static BaseManager * getManager() { return new T();}
};

BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();

文字列で取得したい場合は、文字列から関数ポインタへの標準マップを作成できます。動作する実装を次に示します。

#include <map>
#include <string>

class BaseManager
{
public:
    virtual void doSomething() = 0;
};

class DerivedManager1 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class DerivedManager2 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class ManagerFactory
{
public:
    typedef BaseManager * (*GetFunction)();
    typedef std::map<std::wstring, GetFunction> ManagerFunctionMap;
private:
    static ManagerFunctionMap _managers;

public:
    template <typename T> static BaseManager * getManager() { return new T();}
    template <typename T> static void registerManager(const std::wstring& name)
    {
        _managers[name] = ManagerFactory::template getManager<T>;
    }
    static BaseManager * getManagerByName(const std::wstring& name)
    {
        if(_managers.count(name))
        {
            return _managers[name]();
        }
        return NULL;
    }
};
// the static map needs to be initialized outside the class
ManagerFactory::ManagerFunctionMap ManagerFactory::_managers;


int _tmain(int argc, _TCHAR* argv[])
{
    // you can get with the templated function
    BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();
    manager1->doSomething();
    // or by registering with a string
    ManagerFactory::template registerManager<DerivedManager1>(L"Derived1");
    ManagerFactory::template registerManager<DerivedManager2>(L"Derived2");
    // and getting them
    BaseManager * manager2 = ManagerFactory::getManagerByName(L"Derived2");
    manager2->doSomething();
    BaseManager * manager3 = ManagerFactory::getManagerByName(L"Derived1");
    manager3->doSomething();
    return 0;
}

編集:他の回答を読んで、これはDave Van den EyndeのFactorySystemソリューションに非常に似ていることに気付きましたが、テンプレートファクトリクラスをインスタンス化する代わりに関数テンプレートポインターを使用しています。私のソリューションはもう少し軽量だと思います。静的関数のため、インスタンス化される唯一のオブジェクトはマップ自体です。ファクトリが他の機能(DestroyManagerなど)を実行する必要がある場合、彼のソリューションはより拡張性があると思います。

Managerクラスのインスタンスを返す静的メソッドを使用してオブジェクトファクトリを実装できます。ファクトリーでは、マネージャーのデフォルトタイプのメソッドと、マネージャークラスのタイプを表す引数を指定する(列挙型などの)任意のタイプのマネージャーのメソッドを作成できます。この最後のメソッドは、クラスではなくインターフェイスを返す必要があります。

編集:いくつかのコードを提供しようとしますが、C ++の時代はかなり前に戻っており、当分の間はJavaといくつかのスクリプトのみを実行していることに注意してください。

class Manager { // aka Interface
    public: virtual void someMethod() = 0;
};

class Manager1 : public Manager {
    void someMethod() { return null; }
};

class Manager2 : public Manager {
    void someMethod() { return null; }
};

enum ManagerTypes {
    Manager1, Manager2
};

class ManagerFactory {
    public static Manager* createManager(ManagerTypes type) {
        Manager* result = null;
        switch (type) {
        case Manager1:
             result = new Manager1();
             break;
        case Manager2:
             result = new Manager2();
             break;
        default:
             // Do whatever error logging you want
             break;
        }
        return result;
     }
 };

これで、次の方法でFactoryを呼び出すことができるはずです(コードサンプルを機能させることができた場合):

Manager* manager = ManagerFactory.createManager(ManagerTypes.Manager1);

ファクトリクラスのポイントが見えないため、次のようなテンプレートを使用します。

class SomeOtherManager;

class SomeManagerClass {
public:
    SomeManagerClass(SomeOtherManager*);
    virtual void someMethod1();
    virtual void someMethod2();
};


class TheBaseManager {
public:
      // 
};

template <class ManagerClassOne, class ManagerClassOther> 
class SpecialManager : public TheBaseManager {
    public:
        virtual ManagerClassOne* someManagerClass() const;
        virtual ManagerClassOther* someOtherManager() const;
};

TheBaseManager* ourManager = new SpecialManager<SomeManagerClass,SomeOtherManager>;

次のチュートリアルをご覧ください。 http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror= fastbull

C ++で抽象ファクトリを実装するための優れたチュートリアルが含まれており、それに付属するソースコードも非常に堅牢です

クリス

Mh私は100パーセントを理解していません。私は本や記事から工場のものにはあまり興味がありません。


すべてのマネージャーが同様のインターフェースを共有している場合、基本クラスから派生し、プログラムでこの基本クラスを使用できます。 どのクラスを作成するかを決定する場所に応じて、作成に識別子を使用するか(上記を参照)、内部でインスタンス化するマネージャーの決定を処理する必要があります。


別の方法は、「ポリシー」を実装することです。テンプレートを使用するなど。 You ManagerClass :: create()が特定のSomeOtherManagerWhateverインスタンスを返すようにします。これにより、マネージャーを使用するコードでどのマネージャーを決定するかが決まります。これは意図されていません。

またはその方法:


template<class MemoryManagment>
class MyAwesomeClass
{
    MemoryManagment m_memoryManager;
};

(またはそのようなもの) この構成体を使用すると、MyAwesomeClassのインスタンス化を変更するだけで、他のマネージャーを簡単に使用できます。


また、この目的のためのクラスは少し上にあるかもしれません。あなたの場合、ファクトリ関数は私が推測するでしょう。それは個人的な好みの問題です。

動的にリンクされるプラグインのサポートを計画している場合、プログラムは安定したABI(Application Binary Interface)を提供する必要があります。つまり、C ++には標準ABIがないため、C ++をメインインターフェイスとして使用できません。

自分で定義したインターフェイスをプラグインで実装する場合は、インターフェイスのヘッダーファイルをプラグインプログラマに提供し、オブジェクトを作成および削除するために非常に単純なCインターフェイスで標準化する必要があります。

「新規」を可能にする動的ライブラリを提供することはできません。プラグインクラスはそのままです。そのため、オブジェクトを作成するためにCインターフェイスで標準化する必要があります。 STLコンテナのような互換性のない型を引数で使用しない限り、C ++オブジェクトを使用できます。 STL実装があなたのものと同じであることを保証できないため、別のライブラリから返されたベクターを使用することはできません。

Manager.h

class Manager
{
public:
  virtual void doSomething() = 0;
  virtual int doSomethingElse() = 0;
}

extern "C" {
Manager* newManager();
void deleteManager(Manager*);
}

PluginManager.h

#include "Manager.h"

class PluginManager : public Manager
{
public:
  PluginManager();
  virtual ~PluginManager();

public:
  virtual void doSomething();
  virtual int doSomethingElse();
}

PluginManager.cpp

#include "PluginManager.h"

Manager* newManager()
{
  return new PluginManager();
}
void deleteManager(Manager* pManager)
{
  delete pManager;
}

PluginManager::PluginManager()
{
  // ...
}

PluginManager::~PluginManager()
{
  // ...
}

void PluginManager::doSomething()
{
  // ...
}

int PluginManager::doSomethingElse()
{
  // ...
}

TheManagerについては話しませんでした。どのクラスを使用するかを制御する必要があるように見えますか?または、それらを連結しようとしていますか?

抽象ベースクラスと現在使用されているクラスへのポインタが必要なようです。連鎖したい場合は、抽象クラスとマネージャクラスの両方で連鎖できます。抽象クラスの場合、チェーン内の次のクラスにメンバーを追加します。マネージャーがリストで使用する順番に並べ替える場合マネージャーでaddMe()が必要になるように、クラスを追加する方法が必要です。あなたが何を選択しているかを知っているように聞こえます。 addMe funcを含むリストが推奨です。アクティブなクラスが1つだけ必要な場合は、TheManagerでそれを決定する関数が適切です。

これは必要以上に重いかもしれませんが、プラグインをサポートするフレームワーククラスを作成しようとしているようです。

3つのセクションに分割します。

1)FrameWorkクラスがプラグインを所有します。 このクラスは、プラグインによって提供されるインターフェースの公開を担当します。

2)PlugInクラスは、作業を行うコンポーネントを所有します。 このクラスは、エクスポートされたインターフェースを登録し、インポートされたインターフェースをコンポーネントにバインドします。

3)3番目のセクション、コンポーネントはインターフェースのサプライヤとコンシューマです。

物事を拡張可能にするために、物事を立ち上げて実行することは、段階に分けられるかもしれません。

  1. すべてを作成します。
  2. すべてを接続します。
  3. すべてを開始します。

物事を分解する。

  1. すべてを停止します。
  2. すべてを破壊します。
class IFrameWork {
public:
    virtual ~IFrameWork() {}
    virtual void RegisterInterface( const char*, void* ) = 0;
    virtual void* GetInterface( const char* name ) = 0;
};

class IPlugIn {
public:
    virtual ~IPlugIn() {}
    virtual void BindInterfaces( IFrameWork* frameWork ) {};
    virtual void Start() {};
    virtual void Stop() {};
};

struct SamplePlugin :public IPlugIn {
    ILogger* logger;

    Component1 component1;
    WebServer  webServer;

public:
    SamplePlugin( IFrameWork* frameWork ) 
        :logger( (ILogger*)frameWork->GetInterface( "ILogger" ) ),  //assumes the 'System' plugin exposes this
        component1(),
        webServer( component1 )
    {
        logger->Log( "MyPlugin Ctor()" );

        frameWork->RegisterInterface( "ICustomerManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IVendorManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IAccountingManager", dynamic_cast( &webServer ) ); 
    }

    virtual void BindInterfaces( IFrameWork* frameWork ) {
        logger->Log( "MyPlugin BindInterfaces()" );

        IProductManager* productManager( static_cast( frameWork->GetInterface( "IProductManager" ) ) );
        IShippingManager* shippingManager( static_cast( frameWork->GetInterface( "IShippingManager" ) ) );

        component1.BindInterfaces( logger, productManager );
        webServer.BindInterfaces( logger, productManager, shippingManager );
    }

    virtual void Start() {
        logger->Log( "MyPlugin Start()" );

        webServer.Start();
    }

    virtual void Stop() {
        logger->Log( "MyPlugin Stop()" );

        webServer.Stop();
    }
};

class FrameWork :public IFrameWork {
    vector plugIns;
    map interfaces;
public:
    virtual void RegisterInterface( const char* name, void* itfc ) {
        interfaces[ name ] = itfc;
    }
    virtual void* GetInterface( const char* name )  {
        return interfaces[ name ];
    }

    FrameWork() {
        //Only interfaces in 'SystemPlugin' can be used by all methods of the other plugins
        plugIns.push_back( new SystemPlugin( this ) );

        plugIns.push_back( new SamplePlugin( this ) ); 
        //add other plugIns here

        for_each( plugIns.begin(), plugIns.end(), bind2nd( mem_fun( &IPlugIn::BindInterfaces ), this ) );
        for_each( plugIns.begin(), plugIns.end(), mem_fun( &IPlugIn::Start ) );
    }

    ~FrameWork() {
        for_each( plugIns.rbegin(), plugIns.rend(), mem_fun( &IPlugIn::Stop ) );
        for_each( plugIns.rbegin(), plugIns.rend(), Delete() );
    }
};

これは、約15分で思いついた最小限のファクトリーパターンの実装です。より高度な基本クラスを使用する同様のものを使用します。

#include "stdafx.h"
#include <map>
#include <string>

class BaseClass
{
public:
    virtual ~BaseClass() { }
    virtual void Test() = 0;
};

class DerivedClass1 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class DerivedClass2 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class IFactory
{
public:
    virtual BaseClass* CreateNew() const = 0;
};

template <typename T>
class Factory : public IFactory
{
public:
    T* CreateNew() const { return new T(); }
};

class FactorySystem
{
private:
    typedef std::map<std::wstring, IFactory*> FactoryMap;
    FactoryMap m_factories;

public:
    ~FactorySystem()
    {
        FactoryMap::const_iterator map_item = m_factories.begin();
        for (; map_item != m_factories.end(); ++map_item) delete map_item->second;
        m_factories.clear();
    }

    template <typename T>
    void AddFactory(const std::wstring& name)
    {
        delete m_factories[name]; // Delete previous one, if it exists.
        m_factories[name] = new Factory<T>();
    }

    BaseClass* CreateNew(const std::wstring& name) const
    {
        FactoryMap::const_iterator found = m_factories.find(name);
        if (found != m_factories.end())
            return found->second->CreateNew();
        else
            return NULL; // or throw an exception, depending on how you want to handle it.
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    FactorySystem system;
    system.AddFactory<DerivedClass1>(L"derived1");
    system.AddFactory<DerivedClass2>(L"derived2");

    BaseClass* b1 = system.CreateNew(L"derived1");
    b1->Test();
    delete b1;
    BaseClass* b2 = system.CreateNew(L"derived2");
    b2->Test();
    delete b2;

    return 0;
}

そのままコピー&amp; VS2005 / 2008の最初のWin32コンソールアプリに貼り付けます。何かを指摘したい:

  • クラスごとに具体的なファクトリを作成する必要はありません。テンプレートがそれを行います。
  • ファクトリオブジェクト全体を独自のクラスに配置して、ファクトリオブジェクトの作成や削除について心配する必要がないようにします。クラスを登録するだけで、コンパイラによってファクトリクラスが作成され、パターンによってファクトリオブジェクトが作成されます。その寿命の終わりに、すべての工場はきれいに破壊されます。工場の寿命を誰が管理しているかについて混乱がないため、この形式のカプセル化が気に入っています。
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top