代码之家  ›  专栏  ›  技术社区  ›  ctacke

寻找一个更好的C++类工厂

  •  6
  • ctacke  · 技术社区  · 17 年前

    我有一个应用程序有几个对象(到目前为止大约有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 回复  |  直到 17 年前
        1
  •  9
  •   Community Mohan Dere    8 年前

    是的,有办法。即使在C++中,C代码也很简单(不检查继承):

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

    这和C代码一样有效。它也是类型安全的:如果您传递的类型不是从brokeredObject继承的(或者不是该类型本身),那么编译器会对返回语句抱怨。但是,它将始终返回一个新对象。

    独生子女

    正如另一个家伙所建议的(归功于他),这一切看起来非常像一个很好的情况为单身模式。只要做 TypeA::getInstance() 以获取存储在该类的静态变量中的一个实例和单个实例。我想这会比上面的方法简单得多,不需要ID来解决它(我之前展示了一种使用模板在这个答案中存储ID的方法,但我发现它实际上就是单例的方法)。

    我已经读过了,您将有机会拥有这些类的多个实例。一种方法是 明顿 (我编造了那个词:)

    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() 它关心细节。共享指针的singleton案例中的自定义删除程序使删除成为no op,因为共享指针拥有的对象是静态分配的。但是,要注意静态变量的破坏顺序问题: Static initialization order fiasco

        2
  •  5
  •   Shane Powell    17 年前

    我将解决这个问题的方法是使用我称之为静态注册表模式,在我的脑海中是依赖注入的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类不需要知道这一点。
    • 这个实现可以在库中使用,并且您可以在每个项目级别上控制使用许多不同的技术将哪些构建器拉入到项目中。
    • 建设者可以像您希望的那样复杂或简单(如上所述)。

    欺骗:

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

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

        3
  •  3
  •   Loki Astari    17 年前

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

    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
  •  3
  •   Boyan    17 年前

    代替brokeredObject基类中的getInterfaceID(),您可以定义纯虚拟方法:

    virtual BrokeredObject& GetInstance()=0;
    

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

        5
  •  2
  •   David Norman    17 年前

    看起来您不需要全局对象来进行管理,那么为什么不把所有东西都转移到类本身呢?

    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();
    }
    
        6
  •  1
  •   Mark Ransom    17 年前

    如果你有 RTTI 启用后,可以使用 typeid .

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


    编辑:好的,所以你不想被关在单人间,没问题。C++的奇妙之处在于它给了你这么多的灵活性。您可以有一个getsharedInstance()成员函数,该函数返回类的静态实例,但保持构造函数为公共的,这样您仍然可以创建其他实例。
        7
  •  1
  •   dalle    17 年前

    如果在编译时总是知道类型,那么调用 BrokeredObject* p = GetObjectByID(TypeA::get_InterfaceID()) 而不是 TypeA* p = new TypeA TypeA 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 作为类型标识:

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

    当然,在任何情况下,每个新类都可以在注册表中进行自注册,从而使注册分散而不是集中。

        8
  •  0
  •   Bill K    17 年前

    几乎可以肯定,您应该使用依赖注入。

        9
  •  0
  •   ABCD    17 年前

    为什么不呢?

        template 
        BrokeredObject* GetOrCreateObject()
        {
          return new T();
        }
    
        10
  •  0
  •   Community Mohan Dere    8 年前

    我的用例往往会变得更复杂一点——我需要进行一点对象初始化的能力,并且我需要能够根据配置从不同的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
    {
    };