寻找更好的C ++类工厂

时间:2008-12-12 17:12:20

标签: c++ design-patterns

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

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

class BrokeredObject
{
  virtual int GetInterfaceId() = 0;
};

每个对象类型都返回一个唯一的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;
}

我觉得痛苦的是维护这个ID列表并让每个类实现它。我至少通过让每个类型都拥有关于它自己的ID的信息来使我的消费者的生活变得更轻松:

class TypeA : public BrokeredObject
{
  static int get_InterfaceID() { return IID_TYPEA; }
  int GetInterfaceID() { return get_InterfaceID(); }
};

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

GetObjectByID(TypeA::get_InterfaceID());

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

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

BrokeredObject GetOrCreateObject<T>() where T : BrokeredObject
{
  return new T();
}

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

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

10 个答案:

答案 0 :(得分:9)

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

template<typename T>
BrokeredObject * GetOrCreateObject() {
  return new T();
}

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

的Singleton

正如另一个人建议的那样(相信他),这一切看起来非常像单身人士模式的优秀案例。只需执行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();
}

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

答案 1 :(得分: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;

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;
}

优点:

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

缺点:

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

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

答案 2 :(得分:3)

而不是BrokeredObject基类中的GetInterfaceId(),您可以定义纯虚方法:

virtual BrokeredObject& GetInstance()=0;

在派生类中,你将从该方法返回特定派生类的实例,如果它已经创建,如果没有,你将首先创建它然后返回它。

答案 3 :(得分:3)

使用模板类作为经纪人 使实例成为函数的静态成员。它将在首次使用时创建,并在程序退出时自动销毁。

template <class Type>
class BrokeredObject
{
    public:
        static Type& getInstance()
        {
            static Type theInstance;

            return theInstance;
        }
}; 

class TestObject
{
    public:
       TestObject()
       {}
};


int main()
{
    TestObject& obj =BrokeredObject<TestObject>::getInstance();
}

答案 4 :(得分:2)

看起来您不需要全局对象来进行管理,那么为什么不将所有内容都移到类中呢?

template <class Type>
class BrokeredObject
{
protected:
    static Type *theInstance;

public:
    static Type *getOrCreate()
    {
        if (!theInstance) {
            theInstance = new Type();
        }

        return theInstance;
    }

    static void free()
    {
        delete theInstance;
    }

};

class TestObject : public BrokeredObject<TestObject>
{
public:
    TestObject()
    {}

};


int
main()
{
    TestObject *obj = TestObject::getOrCreate();
}

答案 5 :(得分:1)

如果您启用了RTTI,则可以使用typeid获取班级名称。

有一个问题,为什么你使用工厂而不是每个类都使用单例模式?

<小时/> 编辑:好的,所以你不想被锁定成单身人士;没问题。关于C ++的精彩之处在于它为您提供了如此多的灵活性。您可以拥有一个GetSharedInstance()成员函数,该函数返回该类的静态实例,但将构造函数保留为public,以便您仍然可以创建其他实例。

答案 6 :(得分:1)

如果您在编译时始终知道类型,那么直接调用BrokeredObject* p = GetObjectByID(TypeA::get_InterfaceID())而不是TypeA* p = new TypeATypeA o几乎没有意义。

如果你另一方面在编译时不知道确切的类型,你可以使用某种类型的注册表。

template <class T>
BrokeredObject* CreateObject()
{
    return new T();
}

typedef int type_identity;
typedef std::map<type_identity, BrokeredObject* (*)()> registry;
registry r;

class TypeA : public BrokeredObject
{
public:
     static const type_identity identity;
};

class TypeB : public BrokeredObject
{
public:
     static const type_identity identity;
};

r[TypeA::identity] = &CreateObject<TypeA>;
r[TypeB::identity] = &CreateObject<TypeB>;

或者如果您启用了RTTI,则可以使用type_info作为type_identity:

typedef const type_info* type_identity;
typedef std::map<type_identity, BrokeredObject* (*)()> registry;
registry r;

r[&typeid(TypeA)] = &CreateObject<TypeA>;
r[&typeid(TypeB)] = &CreateObject<TypeB>;

在任何情况下,每个新课程都可以在注册表中自行注册,使注册分散而不是集中注册。

答案 7 :(得分:0)

你几乎肯定会使用依赖注入。

答案 8 :(得分:0)

为什么不呢?

    template 
    BrokeredObject* GetOrCreateObject()
    {
      return new T();
    }

答案 9 :(得分: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
};

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

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

class SimEepromImpl: public BrokeredObject<SimEepromImpl>, public CSimEeprom
{
};