studio programacion para móviles libro edición desarrollo curso aplicaciones c++ design-patterns

programacion - Buscando una mejor fábrica de clase C++



manual de programacion android pdf (10)

¿Por qué no esto?

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

Tengo una aplicación que tiene varios objetos (aproximadamente 50 hasta ahora, pero está creciendo). Solo hay una instancia de cada uno de estos objetos en la aplicación y estas instancias se comparten entre los componentes.

Lo que he hecho es derivar todos los objetos de una clase base BrokeredObject:

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

Y cada tipo de objeto devuelve una ID única. Estas identificaciones se mantienen en un archivo de encabezado.

Luego tengo una "fábrica" ​​ObjectBroker. Cuando alguien necesita un objeto, llama a GetObjectByID (). El boker busca en una lista STL para ver si el objeto ya existe, si lo hace, lo devuelve. Si no, lo crea, lo pone en la lista y lo devuelve. Todo bien y bien

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

Lo que me resulta doloroso es mantener esta lista de ID y tener que hacer que cada clase lo implemente. Al menos, he hecho que la vida de mis consumidores sea un poco más fácil al hacer que cada tipo tenga información sobre su propia identificación como esta:

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

Entonces puedo obtener un objeto como este:

GetObjectByID(TypeA::get_InterfaceID());

Tengo que saber realmente qué es el mapeo de identificación, pero todavía no estoy contento con el mantenimiento y la posibilidad de errores. Parece que si conozco el tipo, ¿por qué debería también saber la identificación?

Lo que anhelo es algo así en C #:

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

Donde ObjectBroker crearía el objeto en función del tipo pasado.

¿C # me ha echado a perder y es solo un hecho de la vida que C ++ no puede hacer esto o hay una manera de lograr esto que no estoy viendo?


Es casi seguro que estés usando inyección de dependencia.


No parece que necesites el objeto global para hacer la gestión, ¿por qué no mover todo a las clases?

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


Si siempre conoce el tipo en tiempo de compilación, tiene poco sentido llamar a BrokeredObject* p = GetObjectByID(TypeA::get_InterfaceID()) lugar de TypeA* p = new TypeA o TypeA o directamente.

Si, por otro lado, no conoce el tipo exacto en el momento de la compilación, podría usar algún tipo de registro de tipo.

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

o si tiene RTTI habilitado, puede usar type_info como 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>;

Cada clase nueva podría, por supuesto, registrarse en el registro, haciendo que el registro se descentralice en lugar de centralizarse.


Si tiene RTTI habilitado, puede obtener el nombre de clase usando typeid .

Una pregunta, ¿por qué estás usando una fábrica en lugar de usar un patrón único para cada clase?

Editar: OK, por lo que no quiere que lo bloqueen en un singleton; No hay problema. Lo maravilloso de C ++ es que te da mucha flexibilidad. Podría tener una función de miembro GetSharedInstance () que devuelva una instancia estática de la clase, pero deje el constructor público para que pueda crear otras instancias.

Use una clase de plantilla como intermediario.
Haga que la instancia sea un miembro estático de la función. Se creará en el primer uso y se destruirá automágicamente cuando el programa salga.

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


En lugar de GetInterfaceId () en la clase base BrokeredObject, podría definir ese método virtual puro:

virtual BrokeredObject& GetInstance()=0;

Y en las clases derivadas, regresará de ese método la instancia de la clase derivada particular, si ya está creada, de lo contrario, primero la creará y luego la devolverá.


La forma en que resolvería este problema es usar lo que yo llamaría el Patrón del Registro Estático, que en mi opinión es la versión C ++ de la inyección de dependencia.

Básicamente, tiene una lista estática de objetos de construcción de un tipo que usa para construir objetos de otro tipo.

Una implementación de registro estático básico se vería así:

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;

Una implementación de BrokeredObjectBuilder podría verse así:

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

Pros:

  • Todo el código que conoce sobre la creación de los tipos está separado en los constructores y las clases BrokeredObject no necesitan saber al respecto.
  • Esta implementación se puede usar en bibliotecas y puede controlar, a nivel de proyecto, qué constructores se insertan en un proyecto usando varias técnicas diferentes.
  • Los constructores pueden ser tan complejos o simples (como arriba) como usted quiera.

Contras:

  • Hay un poquito de infraestructura involucrada (pero no demasiado).
  • La flexibilidad de definir las variables globales para incluir qué constructores incluir en su proyecto hace que sea un poco complicado trabajar con ellos.
  • Encuentro que a las personas les cuesta entender este patrón, no estoy seguro por qué.
  • A veces no es fácil saber qué hay en el registro estático en un momento dado.
  • La implementación anterior pierde un bit de memoria. (Puedo vivir con ello...)

La implementación anterior es muy simple, puede ampliarla de muchas maneras diferentes según los requisitos que tenga.


Mi caso de uso tendía a ser un poco más complejo: necesitaba la capacidad de hacer un poco de inicialización de objetos y necesitaba poder cargar objetos de diferentes archivos DLL según la configuración (por ejemplo, simulada versus real para el hardware). Comenzó a parecerse a COM y ATL era hacia donde me dirigía, pero no quería agregar el peso de COM al SO (esto se está haciendo en CE).

Lo que terminé yendo fue basado en plantillas (gracias gracias por ponerme en la pista) y se ve así:

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

Luego, para garantizar que los objetos (dado que están modelados) realmente se compilan, agregué definiciones como estas:

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


Sí, hay una manera. Un método bastante simple, incluso en C ++, para lo que hace ese código C # (sin verificar la herencia):

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

Esto funcionará y hará lo mismo que el código C #. También es seguro para tipos: si el tipo que pasa no se hereda de BrokeredObject (o no es ese tipo en sí), entonces el compilador gime en la declaración de devolución. Sin embargo, siempre devolverá un nuevo objeto.

Semifallo

Como sugirió otro tipo (créditos para él), todo esto se parece mucho a un buen caso para el patrón singleton. Simplemente haga que TypeA::getInstance() obtenga la instancia única almacenada en una variable estática de esa clase. Supongo que sería mucho más fácil que la forma anterior, sin la necesidad de ID para resolverlo (anteriormente mostré una forma de usar plantillas para almacenar ID en esta respuesta, pero lo encontré efectivamente es solo lo que es un singleton).

He leído que dejará la posibilidad de tener múltiples instancias de las clases. Una forma de hacerlo es tener un Mingleton ( inventé esa palabra :))

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

Ahora, simplemente usa SomeClass::getOrCreate() y se preocupa por los detalles. El eliminador personalizado en el caso de singleton para shared_ptr hace que la eliminación sea no operativa, porque el objeto propiedad de shared_ptr se asigna estáticamente. Sin embargo, tenga en cuenta los problemas de orden de destrucción de las variables estáticas: Fiasco de orden de inicialización estática