寻找更好的C++类工厂

cta*_*cke 7 c++ design-patterns

我有一个应用程序有几个对象(目前约有50个,但正在增长).应用程序中每个对象只有一个实例,这些实例在组件之间共享.

我所做的是从基础BrokeredObject类派生所有对象:

class BrokeredObject
{
  virtual int GetInterfaceId() = 0;
};
Run Code Online (Sandbox Code Playgroud)

每个对象类型都返回一个唯一的ID.这些ID保存在头文件中.

然后我有一个ObjectBroker"工厂".当有人需要一个对象时,请调用GetObjectByID().boker在STL列表中查看该对象是否已存在,如果存在,则返回该对象.如果没有,它会创建它,将其放入列表并返回它.一切都很好.

BrokeredObject *GetObjectByID(int id)
{
  BrokeredObject *pObject;
  ObjectMap::iterator = m_objectList.find(id);
  // etc.
  if(found) return pObject;

  // not found, so create
  switch(id)
  {
    case 0: pObject = new TypeA; break;
    case 1: pObject = new TypeB; break;
    // etc.
    // I loathe this list
  }
  // add it to the list
  return pObject;
}
Run Code Online (Sandbox Code Playgroud)

我觉得痛苦的是维护这个ID列表并让每个类实现它.我至少让我的消费者的生活变得更容易了,让每个类型都拥有关于它自己的ID的信息,如下所示:

class TypeA : public BrokeredObject
{
  static int get_InterfaceID() { return IID_TYPEA; }
  int GetInterfaceID() { return get_InterfaceID(); }
};
Run Code Online (Sandbox Code Playgroud)

所以我可以得到一个像这样的对象:

GetObjectByID(TypeA::get_InterfaceID());
Run Code Online (Sandbox Code Playgroud)

有必要实际知道ID映射是什么,但我仍然对维护和错误的可能性感到不满.似乎如果我知道类型,为什么我还要知道ID?

我渴望的是C#中的这样的东西:

BrokeredObject GetOrCreateObject<T>() where T : BrokeredObject
{
  return new T();
}
Run Code Online (Sandbox Code Playgroud)

ObjectBroker将根据传入的类型创建对象.

C#是否已经破坏了我,C++无法做到这一点,或者有没有办法实现这一点,我没有看到它?

Joh*_*itb 9

是的,有一种方法.即使在C++中,C#代码的功能也非常简单(不检查继承):

template<typename T>
BrokeredObject * GetOrCreateObject() {
  return new T();
}
Run Code Online (Sandbox Code Playgroud)

这将与C#代码一样工作和执行.它也是类型安全的:如果你传递的类型不是从BrokeredObject继承的(或者不是那个类型本身),那么编译器会在return语句中发出声音.但是它总是会返回一个新对象.

独生子

正如另一个人建议的那样(相信他),这一切看起来非常像单身人士模式的优秀案例.只是TypeA::getInstance()将单个和单个实例存储在该类的静态变量中.我想这将比上述方法容易得多,而不需要ID来解决它(我之前展示了一种使用模板在这个答案中存储ID的方法,但我发现它实际上只是单身人士).

我已经读过你将有机会打开多个类的实例.一种方法是拥有一个Mingleton(我编造了这个词:))

enum MingletonKind {
    SINGLETON,
    MULTITON
};

// Singleton
template<typename D, MingletonKind>
struct Mingleton {
    static boost::shared_ptr<D> getOrCreate() {
        static D d;
        return boost::shared_ptr<D>(&d, NoopDel());
    }

    struct NoopDel {
        void operator()(D const*) const { /* do nothing */ }
    };
};

// Multiton
template<typename D>
struct Mingleton<D, MULTITON> {
    static boost::shared_ptr<D> getOrCreate() {
        return boost::shared_ptr<D>(new D);
    }
};

class ImASingle : public Mingleton<ImASingle, SINGLETON> {
public:
    void testCall() { }
    // Indeed, we have to have a private constructor to prevent
    // others to create instances of us.
private:
    ImASingle() { /* ... */ }
    friend class Mingleton<ImASingle, SINGLETON>;
};

class ImAMulti : public Mingleton<ImAMulti, MULTITON> {
public:
    void testCall() { }
    // ...
};

int main() {
    // both do what we expect.
    ImAMulti::getOrCreate()->testCall();
    ImASingle::getOrCreate()->testCall();
}
Run Code Online (Sandbox Code Playgroud)

现在,您只需使用SomeClass::getOrCreate()它就会关注细节.对于shared_ptr,单例情况下的自定义删除操作使删除成为无操作,因为shared_ptr拥有的对象是静态分配的.但是,请注意静态变量的破坏顺序问题:静态初始化顺序fiasco


Sha*_*ell 5

我解决这个问题的方法是使用我称之为静态注册表模式的东西,在我看来,它是依赖注入的C++版本.

基本上,您有一个类型的构建器对象的静态列表,您可以使用它来构建另一种类型的对象.

基本的静态注册表实现如下所示:

template <class T>
class StaticRegistry
{
public:
    typedef std::list<T*>   Container;

    static  StaticRegistry<T>&  GetInstance()
    {
        if (Instance == 0)
        {
            Instance = new StaticRegistry<T>;
        }
        return *Instance;
    }

    void    Register(T* item)
    {
        Items.push_back(item);
    }

    void    Deregister(T* item)
    {
        Items.remove(item);
        if (Items.empty())
        {
            delete this;
            Instance = 0;
        }
    }

    typedef typename Container::const_iterator  const_iterator;

    const_iterator begin() const
    {
        return Items.begin();
    }

    const_iterator end() const
    {
        return Items.end();
    }

protected:
    StaticRegistry() {}
    ~StaticRegistry() {}

private:
    Container               Items;

    static StaticRegistry<T>*   Instance;
};

template <class T>
StaticRegistry<T>* StaticRegistry<T>::Instance = 0;
Run Code Online (Sandbox Code Playgroud)

BrokeredObjectBuilder的实现可能如下所示:

class BrokeredObjectBuilderBase {
public:
    BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Register(this); }
    virtual ~BrokeredObjectBuilderBase() { StaticRegistry<BrokeredObjectBuilderBase>::GetInstance().Deregister(this); }

    virtual int GetInterfaceId() = 0;
    virtual BrokeredObject* MakeBrokeredObject() = 0;
};


template<class T>
class BrokeredObjectBuilder : public BrokeredObjectBuilderBase {
public:
    BrokeredObjectBuilder(unsigned long interface_id) : m_InterfaceId(interface_id) { } 
    virtual int GetInterfaceId() { return m_InterfaceId; }
    virtual T* MakeBrokeredObject() { return new T; }
private:
    unsigned long m_InterfaceId;
};


class TypeA : public BrokeredObject
{
   ...
};

// Create a global variable for the builder of TypeA so that it's 
// included in the BrokeredObjectBuilderRegistry
BrokeredObjectBuilder<TypeA> TypeABuilder(TypeAUserInterfaceId);

typedef StaticRegistry<BrokeredObjectBuilderBase> BrokeredObjectBuilderRegistry;

BrokeredObject *GetObjectByID(int id)
{
  BrokeredObject *pObject(0);
  ObjectMap::iterator = m_objectList.find(id);
  // etc.
  if(found) return pObject;

  // not found, so create
  BrokeredObjectBuilderRegistry& registry(BrokeredObjectBuilderRegistry::GetInstance());
  for(BrokeredObjectBuilderRegistry::const_iterator it = registry.begin(), e = registry.end(); it != e; ++it)
  {
    if(it->GetInterfaceId() == id)
    {
      pObject = it->MakeBrokeredObject();
      break;
    }
  }

  if(0 == pObject)
  {
    // userinterface id not found, handle this here
    ...
  }      

  // add it to the list
  return pObject;
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 所有知道创建类型的代码都会分离到构建器中,而BrokeredObject类不需要知道它.
  • 此实现可以在库中使用,您可以在每个项目级别上控制使用多种不同技术将构建器拖入项目中.
  • 构建器可以像您希望的那样复杂或简单(如上所述).

缺点:

  • 有一点点基础设施(但不是太多).
  • 定义全局变量以包含要包含在项目中的构建器的灵活性确实使得它有点混乱.
  • 我发现人们发现很难理解这种模式,我不知道为什么.
  • 在任何时候都不容易知道静态注册表中的内容.
  • 上面的实现泄漏了一点内存.(我可以忍受这一点...)

上面的实现非常简单,您可以根据您的要求以多种不同的方式扩展它.


cta*_*cke 0

我的用例变得更加复杂——我需要能够进行一些对象初始化,并且需要能够根据配置从不同的 DLL 加载对象(例如硬件的模拟与实际)。它开始看起来像 COM 和 ATL 是我的方向,但我不想将 COM 的权重添加到操作系统中(这是在 CE 中完成的)。

我最终选择的是基于模板的(感谢litb让我走上正轨),如下所示:

class INewTransModule
{
  public:
    virtual bool Init() { return true; }
    virtual bool Shutdown() { return true; }
};

template <typename T>
struct BrokeredObject
{
public:
    inline static T* GetInstance()
  {
    static T t;
    return &t;
  }
};

template <> 
struct BrokeredObject<INewTransModule>
{
public:
    inline static INewTransModule* GetInstance()
  {
    static INewTransModule t;
    // do stuff after creation
    ASSERT(t.Init());
    return &t;
  }
};

class OBJECTBROKER_API ObjectBroker
{
  public: 
    // these calls do configuration-based creations
    static ITraceTool  *GetTraceTool();
    static IEeprom     *GetEeprom();
    // etc
};
Run Code Online (Sandbox Code Playgroud)

然后为了确保对象(因为它们是模板化的)实际上得到编译,我添加了如下定义:

class EepromImpl: public BrokeredObject<EepromImpl>, public CEeprom
{
};

class SimEepromImpl: public BrokeredObject<SimEepromImpl>, public CSimEeprom
{
};
Run Code Online (Sandbox Code Playgroud)