2009-08-11 14 views
6

Tengo una situación en la que tengo una interfaz que define cómo se comporta determinada clase para completar un determinado rol en mi programa, pero en este punto vez no estoy 100% seguro de cuántas clases escribiré para desempeñar ese papel. Sin embargo, al mismo tiempo, sé que quiero que el usuario pueda seleccionar, desde un cuadro de lista/combo de GUI, qué clase concreta implementa la interfaz que quiere usar para completar un determinado rol. Quiero que la GUI pueda enumerar todas las clases disponibles, pero preferiría no tener que volver atrás y cambiar el código anterior cada vez que decida implementar una nueva clase para completar ese rol (que puede tardar meses)¿Cómo puedo hacer un seguimiento de (enumerar) todas las clases que implementan una interfaz?

algunas cosas que he considerado:

  1. utilizando una enumeración
    • Pros:
      1. yo sé cómo hacerlo
    • Contras
      1. voy a tener que actualizar actualizar la enumeración cuando agrego una nueva clase
      2. fea para recorrer
  2. utilizando algún tipo de static lista de objetos en la interfaz, y agregar un nuevo elemento desde el archivo de definición de la clase de implementación
    • Pros:
      1. no tendrá que cambiar el código antiguo
    • Contras:
      1. Ni siquiera estoy seguro si esto es posible
      2. No está seguro de qué tipo de información para almacenar de manera que un método de fábrica puede elegir la constructor apropiado (tal vez un mapa entre una cadena y un puntero de función que devuelve un puntero a un objeto de la interfaz)

Supongo que este es un problema (o similar a un problema) que los programadores más experimentados probablemente hayan encontrado antes (y con frecuencia), y probablemente exista una solución común a este tipo de problema, que es casi seguro que es mejor que cualquier cosa con la que sea capaz de llegar. Entonces, ¿cómo lo hago?

(PS he buscado, pero todo lo que encontré fue esto, y no es la misma:. How do I enumerate all items that implement a generic interface? Parece que ya sabe cómo resolver el problema que estoy tratando de averiguar.)

Edit: renombró el título a "¿Cómo puedo hacer un seguimiento de ..." en lugar de solo "¿Cómo puedo enumerar ...?" porque la pregunta original sonaba como si estuviera más interesado en examinar el entorno de tiempo de ejecución, donde lo que soy realmente interesado en la conservación de libros en tiempo de compilación.

+0

¿Qué quiere decir con interfaz? ¿Una clase de interfaz real o un método específico para la clase? – TimW

+0

Una clase de interfaz real: una colección de varios métodos que definen interacciones con un objeto de esa interfaz – cheshirekow

Respuesta

6

Crea un singleton donde puedes registrar tus clases con un puntero a una función de creador. En los archivos cpp de las clases concretas, registra cada clase.
Algo como esto:

class Interface; 
typedef boost::function<Interface*()> Creator; 

class InterfaceRegistration 
{ 
    typedef map<string, Creator> CreatorMap; 
public: 
    InterfaceRegistration& instance() { 
     static InterfaceRegistration interfaceRegistration; 
     return interfaceRegistration; 
    } 

    bool registerInterface(const string& name, Creator creator) 
    { 
     return (m_interfaces[name] = creator); 
    } 

    list<string> names() const 
    { 
     list<string> nameList; 
     transform(
      m_interfaces.begin(), m_interfaces.end(), 
      back_inserter(nameList) 
      select1st<CreatorMap>::value_type>()); 
    } 

    Interface* create(cosnt string& name) const 
    { 
     const CreatorMap::const_iterator it 
      = m_interfaces.find(name); 
     if(it!=m_interfaces.end() && (*it)) 
     { 
      return (*it)(); 
     } 
     // throw exception ... 
     return 0; 
    } 

private: 
    CreatorMap m_interfaces; 
}; 


// in your concrete classes cpp files 
namespace { 
bool registerClassX = InterfaceRegistration::instance("ClassX", boost::lambda::new_ptr<ClassX>()); 
} 

ClassX::ClassX() : Interface() 
{ 
    //.... 
} 

// in your concrete class Y cpp files 
namespace { 
bool registerClassY = InterfaceRegistration::instance("ClassY", boost::lambda::new_ptr<ClassY>()); 
} 

ClassY::ClassY() : Interface() 
{ 
    //.... 
} 
+1

Y puede envolver el registro en una macro para que sea más fácil de usar: REGISTER_CLASS (ClassY) –

+1

Gracias, ese fragmento de código es muy útil. ¿Hay alguna razón particular para usar esta clase extra en lugar de almacenar el mapa como un miembro estático de la interfaz? Por lo que puedo decir, proporciona un método sensible de "crear()", pero por cualquier otro motivo. Esto puede ser especialmente útil como singleton genérico, luego puedo crear una instancia para cada una de las interfaces que sé que extenderé en el futuro. – cheshirekow

+0

+1 para el esfuerzo de codificación :) – neuro

1

Si está en Windows y usa C++/CLI, esto se vuelve bastante fácil. Los .NET Framework proporciona esta capacidad a través de la reflexión, y funciona muy limpiamente en código administrado.

En C++ nativo, esto se vuelve un poco más complicado, ya que no hay una manera simple de consultar la biblioteca o la aplicación para obtener información sobre el tiempo de ejecución. Hay muchos frameworks that provide this (solo busque marcos de IoC, DI o plugin), pero la forma más sencilla de hacerlo usted mismo es tener algún tipo de configuración que un método de fábrica pueda usar para registrarse y devolver una implementación de su base específica clase. Solo necesitaría implementar la carga de un archivo DLL y registrar el método de fábrica; una vez que lo tiene, es bastante fácil.

+0

Estoy en Windows, pero, por desgracia, el programa también debe ejecutarse en Linux (así que estoy usando el compilador GNU). No necesito ir tan lejos como para consultar el tiempo de ejecución. Preferiría, de hecho, una solución en tiempo de compilación (o más bien, una solución de tiempo de enlace). – cheshirekow

0

No hay forma de consultar las subclases de una clase en C++ (nativo). ¿Cómo se crean las instancias? Considere usar un Método de Fábrica que le permita iterar sobre todas las subclases con las que está trabajando. Cuando crea una instancia como esta, no será posible olvidar agregar una nueva subclase más tarde.

+0

Eso es exactamente lo que quiero hacer, pero quiero una manera limpia de guardar los libros, así que no tengo que actualizar el código de ese método de fábrica cada vez que escribo una nueva subclase. – cheshirekow

3

Recuerdo vagamente hacer algo similar a esto hace muchos años. Su opción (2) es más o menos lo que hice. En ese caso, era std::map de std::string a std::typeinfo. En cada uno, .cpp archivo que he registrado la clase como esta:

static dummy = registerClass (typeid (MyNewClass)); 

registerClass toma un objeto type_info y simplemente devuelve true. Debe inicializar una variable para asegurarse de llamar al registerClass durante el tiempo de inicio. Simplemente llamando al registerClass en el espacio de nombres global es un error. Y hacer dummy estático le permite reutilizar el nombre en unidades de compilación sin una colisión de nombre.

+0

Ok, suena bien. No estoy 100% seguro con mi conocimiento sobre la inicialización estática, pero si este es un plan factible, comenzaré a probarlo. El operador de typeid será útil. Volveré y haré de esto la respuesta si logro que funcione. – cheshirekow

+0

Relectura de respuestas Creo que esta solución es la mejor (¿por ahora?). No es necesario desordenar el código con métodos de fábrica, funciona con código existente y es más directo que lo que he propuesto. Como C++ no tiene muchos mecanismos RTTI, no creo que puedas hacerlo mejor ... +1 hombre. – neuro

+0

¿Y cómo se crea una clase registrada? – TimW

1

Algo que se puede considerar es un contador de objetos. De esta forma, no necesita cambiar cada lugar que asigna, sino solo la definición de implementación. Es una alternativa a la solución de fábrica. Considera los pros/contras.

Una manera elegante de hacerlo es usar el CRTP : Curiously recurring template pattern. El ejemplo principal es un contador de tales :)

De esta manera sólo hay que añadir en su aplicación clase concreta:

class X; // your interface 

class MyConcreteX : public counter<X> 
{ 
    // whatever 
}; 

Por supuesto, no es aplicable si se utiliza implementaciones externas no domina .

EDIT:

para manejar el problema exacto que necesita tener un contador que cuenta sólo la primera instancia.

mis 2 centavos

+0

Esa es una forma muy inteligente de abordar el problema. Supongo que el contador se extiende X y contiene un recuento de objetos estáticos, incrementado en la construcción y decrementado en la destrucción. Sin embargo, creo que esto es un poco indirecto y lo que necesito no es realmente un recuento de objetos, sino una forma de enumerar las subclases disponibles. – cheshirekow

+0

No exactamente, eso usa el patrón CRTP que vale la pena mirar. Ver el enlace en mi respuesta ya que se da el código para dicho contador. La idea en mi respuesta fue usar un contador de objetos, que cuenta solo la primera instanciación. Sé que hay inconvenientes como la necesidad de instanciar al menos un objeto (aunque puede ser un muñeco estático), pero parece ser mejor que la solución de método de fábrica. – neuro

2

que se refiere este artículo para implementar un generador de clases de auto-registro similar al que se describe en la respuesta de TIMW, pero tiene un truco de utilizar una clase proxy fábrica de plantilla para manejar el objeto registro.Bien vale la pena un vistazo :)

objetos auto-registro en C++ ->http://www.ddj.com/184410633

Editar

Aquí está la aplicación de prueba que hice (arreglado un poco;):

object_factory. h

#include <string> 
#include <vector> 
// Forward declare the base object class 
class Object; 
// Interface that the factory uses to communicate with the object proxies 
class IObjectProxy { 
public: 
    virtual Object* CreateObject() = 0; 
    virtual std::string GetObjectInfo() = 0; 
}; 
// Object factory, retrieves object info from the global proxy objects 
class ObjectFactory { 
public: 
    static ObjectFactory& Instance() { 
     static ObjectFactory instance; 
     return instance; 
    } 
    // proxies add themselves to the factory here 
    void AddObject(IObjectProxy* object) { 
     objects_.push_back(object); 
    } 
    size_t NumberOfObjects() { 
     return objects_.size(); 
    } 
    Object* CreateObject(size_t index) { 
     return objects_[index]->CreateObject(); 
    } 
    std::string GetObjectInfo(size_t index) { 
     return objects_[index]->GetObjectInfo(); 
    } 

private: 
    std::vector<IObjectProxy*> objects_; 
}; 

// This is the factory proxy template class 
template<typename T> 
class ObjectProxy : public IObjectProxy { 
public: 
    ObjectProxy() { 
     ObjectFactory::Instance().AddObject(this); 
    }   
    Object* CreateObject() { 
     return new T; 
    } 
    virtual std::string GetObjectInfo() { 
     return T::TalkToMe(); 
    };  
}; 

objects.h

#include <iostream> 
#include "object_factory.h" 
// Base object class 
class Object { 
public: 
    virtual ~Object() {} 
}; 
class ClassA : public Object { 
public: 
    ClassA() { std::cout << "ClassA Constructor" << std::endl; } 
    ~ClassA() { std::cout << "ClassA Destructor" << std::endl; } 
    static std::string TalkToMe() { return "This is ClassA"; } 
}; 
class ClassB : public Object { 
public: 
    ClassB() { std::cout << "ClassB Constructor" << std::endl; } 
    ~ClassB() { std::cout << "ClassB Destructor" << std::endl; } 
    static std::string TalkToMe() { return "This is ClassB"; } 
}; 

objects.cpp

#include "objects.h" 
// Objects get registered here 
ObjectProxy<ClassA> gClassAProxy; 
ObjectProxy<ClassB> gClassBProxy; 

main.cpp

#include "objects.h" 
int main (int argc, char * const argv[]) { 
    ObjectFactory& factory = ObjectFactory::Instance(); 
    for (int i = 0; i < factory.NumberOfObjects(); ++i) { 
     std::cout << factory.GetObjectInfo(i) << std::endl; 
     Object* object = factory.CreateObject(i); 
     delete object; 
    } 
    return 0; 
} 

de salida:

This is ClassA 
ClassA Constructor 
ClassA Destructor 
This is ClassB 
ClassB Constructor 
ClassB Destructor 
+0

Pero entonces el registro es implícito y necesita crear una instancia de cada clase concreta.Prefiero el registro explícito sin creación de objetos. – TimW

+0

Crea un proxy de fábrica para cada clase concreta, pero las clases en sí mismas solo se crean cuando es necesario, a través de la fábrica. Voy a editar la respuesta con un código cuando llegue a casa esta noche :) – irh

+0

gracias por la aclaración – TimW

Cuestiones relacionadas