¿Cómo puedo realizar un seguimiento de (enumerar) todas las clases que implementan una interfaz

StackOverflow https://stackoverflow.com/questions/1260954

  •  12-09-2019
  •  | 
  •  

Pregunta

Tengo una situación en la que tengo una interfaz que define cómo se comporta una cierta clase con el fin de llenar un cierto papel en mi programa, pero en este momento no estoy 100% seguro de cuántas clases voy a escribir a cumplir esa función. Sin embargo, al mismo tiempo, sé que quiero que el usuario sea capaz de seleccionar, a partir de un cuadro combinado / lista de interfaz gráfica de usuario, que clase concreta que implementa la interfaz que desea utilizar para llenar un cierto papel. Quiero que la interfaz gráfica de usuario que sea capaz de enumerar todas las clases disponibles, pero preferiría no tener que volver atrás y cambiar el código antiguo cada vez que decide implementar una nueva clase para cumplir esa función (que puede ser de meses a partir de ahora)

Algunas cosas que he considerado:

  1. utilizando una enumeración
    • Pros:
      1. Yo sé cómo hacerlo
    • Contras
      1. I tendrá que actualizar actualizar la enumeración cuando agrego una nueva clase
      2. feo para iterar a través
  2. utilizando algún tipo de objeto de lista static en la interfaz, y la adición de un nuevo elemento dentro del archivo de definición de la clase que implementa
    • Pros:
      1. no tendrá que cambiar el código antiguo
    • Contras:
      1. No es 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 el constructor adecuado (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 esto es un problema (o similar a un problema) que los programadores más experimentados probablemente han encontrado antes (y con frecuencia), y es probable que haya una solución común a este tipo de problema, que es casi ciertamente mejor que todo lo que soy capaz de subir con. Entonces, ¿cómo lo hago?

(PS he buscado, pero todo lo que encontré fue esto, y no es el mismo: ¿Cómo enumerar todos los elementos que implementan una interfaz genérica? . Parece que ya sabe cómo resolver el problema que estoy tratando de averiguar.)

Edit: Me cambió el nombre el título de "¿Cómo puedo realizar un seguimiento de ..." en lugar de "¿Cómo puedo enumerar ..." porque la pregunta original sonaba como si estuviera más interesado en examinar el entorno de ejecución, donde como lo que realmente me interesa es tiempo de compilación contabilidad.

¿Fue útil?

Solución

Crear un producto único donde se puede registrar sus clases con un puntero a una función creadora. En los archivos cpp de las clases concretas que se registre 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()
{
    //....
}

Otros consejos

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 se trataba de un std::map de std::string a std::typeinfo. En cada uno, el archivo .cpp que registró la clase como esta:

static dummy = registerClass (typeid (MyNewClass));

registerClass toma un objeto type_info y simplemente devuelve true. Usted tiene que inicializar una variable para asegurar que registerClass se llama durante el tiempo de inicio. Simplemente llamando registerClass en el espacio de nombres global es un error. Y haciendo dummy estática permiten reutilizar el nombre a través de las unidades de compilación sin un nombre de colisión.

a 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 registro de objetos. Bien vale la pena un vistazo:)

Auto-Registro de objetos 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;
}

salida:

This is ClassA
ClassA Constructor
ClassA Destructor
This is ClassB
ClassB Constructor
ClassB Destructor

Si estás en Windows, y el uso de C ++ / CLI, esto se convierte en bastante fácil. El marco .NET proporciona esta capacidad a través de la reflexión, y funciona de forma muy limpia en código administrado.

En nativo C ++, esto pone un poco poco más complicado, ya que no hay forma sencilla de consultar la biblioteca o solicitud de información de tiempo de ejecución. Hay muchos que proporcionan este (sólo tiene que buscar la COI, DI, o marcos plugin), pero la forma más sencilla de hacerlo uno mismo es tener algún tipo de configuración que un método de fábrica se puede utilizar para registrar a sí mismos, y devolver una implementación de la clase base específica. Usted sólo había necesidad de aplicar la carga de un archivo DLL, y registrar el método de fábrica -. Una vez conseguido eso, es bastante fácil

Algo que se puede considerar es un contador de objetos. De esta manera usted no tiene que cambiar cada lugar de asignar pero sólo definición aplicación. Es una alternativa a la solución de la fábrica. Considere ventajas / desventajas.

Una forma elegante de hacerlo es utilizar la CRTP: Curiosamente recurrente patrón de plantilla . El ejemplo principal es un contador tal:)

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 que no dominan.

EDIT:

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

mis 2 centavos de dólar

No hay ninguna forma de consultar las subclases de una clase en C (nativo) ++. ¿Cómo se crea las instancias? Considere el uso de un método de fábrica que le permite iterar sobre todas las subclases que se está trabajando. Cuando se crea una instancia de este tipo, que no será posible olvidar la adición de una nueva subclase más tarde.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top