Come posso tenere traccia di (elencare) tutte le classi che implementano l'interfaccia

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

  •  12-09-2019
  •  | 
  •  

Domanda

Ho una situazione in cui ho un'interfaccia che definisce come una certa classe si comporta in modo da riempire un certo ruolo nel mio programma, ma a questo punto nel tempo non sto al 100% sicuro quante classi scriverò a riempire quel ruolo. Tuttavia, allo stesso tempo, so che voglio che l'utente sia in grado di selezionare, da una casella combinata / lista GUI, che classe concreta che implementa l'interfaccia che si desidera utilizzare per riempire un certo ruolo. Voglio la GUI per essere in grado di enumerare tutte le classi disponibili, ma io preferirei non dover tornare indietro e cambiare il vecchio codice ogni volta che decide di implementare una nuova classe di riempire quel ruolo (che può essere mesi da oggi)

Alcune cose che ho preso in considerazione:

  1. usando un'enumerazione
    • Pro:
      1. So come si fa
    • Contro
      1. I dovrà aggiornare aggiornare l'enumerazione quando aggiungo una nuova classe
      2. brutto per scorrere
  2. usando qualche tipo di oggetto lista static nell'interfaccia, e l'aggiunta di un nuovo elemento all'interno del file di definizione della classe che implementa
    • Pro:
      1. non dovrete modificare il vecchio codice
    • Contro:
      1. non sono nemmeno sicuro se questo è possibile
      2. Non sono sicuro di che tipo di informazioni per memorizzare in modo che un metodo factory può scegliere il costruttore corretta (forse una mappa tra una corda e un puntatore a funzione che restituisce un puntatore ad un oggetto dell'interfaccia)

Sto indovinando che questo è un problema (o simile ad un problema) che i programmatori più esperti hanno probabilmente incontrato prima (e spesso), e non v'è probabilmente una soluzione comune a questo tipo di problema, che è quasi certamente meglio di ogni altra cosa sono capace di venire con. Così, come posso fare?

(PS Ho cercato, ma tutto quello che ho trovato è stato questo, e non è la stessa: Come faccio a elencare tutti gli elementi che implementano un'interfaccia generica? . sembra che egli sa già come risolvere il problema che sto cercando di capire.)

Edit: ho rinominato il titolo a "Come posso tenere traccia di ..." e non solo "come posso enumerare ...", perché la domanda iniziale sembrava ero più interessato ad esaminare l'ambiente di runtime, in cui come quello che mi interessa veramente è in fase di compilazione della contabilità.

È stato utile?

Soluzione

Crea un Singleton in cui è possibile registrare le vostre classi con un puntatore a una funzione creatore. Nei file cpp delle classi concrete si registra ogni classe.
Qualcosa di simile a questo:

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()
{
    //....
}

Altri suggerimenti

Ricordo vagamente di fare qualcosa di simile a questo molti anni fa. L'opzione (2) è più o meno quello che ho fatto. In quel caso si trattava di un std::map di std::string a std::typeinfo. In ogni, file cpp ho registrato la classe in questo modo:

static dummy = registerClass (typeid (MyNewClass));

registerClass prende un oggetto type_info e semplicemente restituisce true. È necessario inizializzare una variabile per garantire che registerClass viene chiamato durante il tempo di avvio. Semplicemente chiamando registerClass nel namespace globale è un errore. E fare dummy statica permettono di riutilizzare il nome attraverso unità di compilazione senza un nome di collisione.

I di cui al presente articolo per implementare un auto-registrazione class factory simile a quello descritto nella risposta di TIMW, ma ha il trucco elegante di usare una classe proxy su modelli fabbrica per gestire la registrazione oggetto. Vale la pena dare un'occhiata:)

Auto-Registrazione di oggetti in C ++ -> http://www.ddj.com/184410633

Modifica

Ecco l'applicazione di test che ho fatto (riordinato un po ';):

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

uscita:

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

Se siete su Windows, e l'utilizzo di C ++ / CLI, questo diventa abbastanza facile. Il framework .NET fornisce questa funzionalità attraverso la riflessione, e funziona in modo molto pulito in codice gestito.

In C ++ nativo, questo diventa un po 'più complicato, in quanto non c'è modo semplice per interrogare la biblioteca o richiesta di informazioni di runtime. Ci sono molti che forniscono questo (basta cercare CIO, DI, o framework di plugin), ma il modo più semplice di farlo da soli è quello di avere una qualche forma di configurazione che un metodo di fabbrica può utilizzare per registrare se stessi, e restituire un'implementazione della classe base specifica. Faresti solo bisogno di implementare il caricamento di una DLL, e registrare il metodo factory -. Una volta che avete, è abbastanza facile

Qualcosa che si può prendere in considerazione è un contatore oggetto. In questo modo non c'è bisogno di cambiare ogni luogo di allocare, ma solo la definizione di implementazione. E 'un'alternativa alla soluzione di fabbrica. Considerate pro / contro.

Un modo elegante per farlo è quello di utilizzare la CRTP: Curiosamente ricorrente mascherina modello . L'esempio principale è tale contatore:)

In questo modo è sufficiente aggiungere nell'implementazione classe concreta:

class X; // your interface

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

Naturalmente, non è applicabile se si utilizza implementazioni esterni non padrone.

EDIT:

Per gestire il problema esatto è necessario disporre di un contatore che conta solo la prima istanza.

i miei 2 centesimi

Non v'è alcun modo per interrogare le sottoclassi di una classe in C (nativo) ++. Come si fa a creare le istanze? Considerare l'utilizzo di un metodo factory che consente di iterazioni su tutte le sottoclassi si sta lavorando. Quando si crea un'istanza in questo modo, non sarà possibile dimenticare l'aggiunta di una nuova sottoclasse successiva.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top