Comment puis-je garder une trace de (énumérer) toutes les classes qui mettent en œuvre une interface

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

  •  12-09-2019
  •  | 
  •  

Question

J'ai une situation où j'ai une interface qui définit la façon dont une certaine classe se comporte pour remplir un certain rôle dans mon programme, mais à ce moment je ne suis pas sûr à 100% le nombre de classes que je vais écrire remplir ce rôle. Cependant, en même temps, je sais que je veux que l'utilisateur soit en mesure de sélectionner, à partir d'une zone de liste déroulante / liste GUI, qui classe concrète qui implémente l'interface qu'ils veulent utiliser pour remplir un certain rôle. Je veux l'interface graphique pour être en mesure d'énumérer toutes les classes disponibles, mais je préférerais ne pas avoir à revenir en arrière et changer l'ancien code chaque fois que je décide de mettre en œuvre une nouvelle classe pour remplir ce rôle (qui peut être mois à partir de maintenant)

Il y a des choses que j'ai pris en considération:

  1. en utilisant une énumération
    • Plus:
      1. Je sais comment le faire
    • Moins
      1. Je vais mettre à jour mettre à jour l'énumération quand j'ajouter une nouvelle classe
      2. laid à itérer
  2. en utilisant une sorte d'objet de liste static dans l'interface et l'ajout d'un nouvel élément à partir du fichier de définition de la classe implémentant
    • Plus:
      1. ont l'habitude de changer l'ancien code
    • Inconvénients:
      1. Même pas sûr si cela est possible
      2. Je ne sais pas ce genre d'information pour stocker de sorte qu'une méthode de fabrication peut choisir le constructeur approprié (peut-être une carte entre une chaîne et un pointeur de fonction qui renvoie un pointeur vers un objet de l'interface)

Je devine que c'est un problème (ou similaire à un problème) que les programmeurs plus expérimentés ont probablement rencontré avant (et souvent), et il y a probablement une solution commune à ce genre de problème, ce qui est presque certainement mieux que tout ce que je suis capable de venir avec. Alors, comment puis-je faire?

(PS je recherche, mais tout ce que je trouvais était cela, et ce n'est pas le même: Comment puis-je énumérer tous les éléments qui mettent en œuvre une interface générique . Il semble qu'il sait déjà comment résoudre le problème que j'essaie de comprendre.)

Edit: Je renomme le titre « Comment puis-je garder une trace de ... » plutôt que « Comment puis-je énumérer ... » parce que la question initiale sonnait comme si j'étais plus intéressé à examiner l'environnement d'exécution, où comme ce que je suis vraiment intéressé est tenue de livres de compilation.

Était-ce utile?

La solution

Créer un singleton où vous pouvez enregistrer vos classes avec un pointeur vers une fonction de créateur. Dans les fichiers cpp des classes concrètes que vous enregistrez chaque classe.
Quelque chose comme ceci:

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

Autres conseils

Je me souviens vaguement faire quelque chose de semblable à cela il y a de nombreuses années. Votre option (2) est à peu près ce que je faisais. Dans ce cas, il était un std::map de std::string à std::typeinfo. Dans chaque cas, le fichier .cpp Je me suis inscrit la classe comme ceci:

static dummy = registerClass (typeid (MyNewClass));

registerClass prend un objet type_info et retourne simplement true. Vous devez initialiser une variable pour faire en sorte que registerClass est appelé pendant le temps de démarrage. appelant simplement registerClass dans l'espace de noms global est une erreur. Et faire dummy statique vous permettent de réutiliser le nom dans toutes les unités de compilation sans collision de nom.

Je renvoie à cet article pour mettre en œuvre une usine de classe d'auto-enregistrement similaire à celui décrit dans la réponse de TIMW, mais il a l'astuce d'utiliser une classe proxy usine basé sur un modèle pour gérer l'enregistrement des objets. Il vaut bien un coup d'oeil:)

Objets auto-enregistrement en C ++ -> http://www.ddj.com/184410633

Modifier

Voici l'application de test je l'ai fait (tondu un peu;):

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

sortie:

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

Si vous êtes sous Windows, et en C ++ / CLI, cela devient assez facile. Le .NET Framework fournit cette capacité par réflexion, et cela fonctionne très proprement en code managé.

Dans natif C ++, cela devient un peu plus délicat, car il n'y a pas de moyen simple pour interroger la bibliothèque ou à l'application des informations d'exécution. Il y a beaucoup de qui fournissent ce (il suffit de regarder pour IoC, DI, ou cadres plug-in), mais les moyens les plus simples de le faire vous-même est d'avoir une certaine forme de configuration qui une méthode de fabrication peut utiliser pour se faire enregistrer et retourner une implémentation de votre classe de base spécifique. Vous auriez juste besoin de mettre en œuvre le chargement d'une DLL, et l'enregistrement de la fabrique -. Une fois que vous avez cela, il est assez facile

Quelque chose que vous pouvez considérer est un compteur d'objets. De cette façon, vous n'avez pas besoin de changer tous les endroits que vous allouez mais juste définition de mise en œuvre. Il est une alternative à la solution d'usine. Considérez avantages / inconvénients.

Une façon élégante de le faire consiste à utiliser le CRTP: Curieusement motif de modèle récurrent . L'exemple principal est un tel compteur:)

De cette façon, il vous suffit d'ajouter dans votre implémentation de classe concrète:

class X; // your interface

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

Bien sûr, ce n'est pas applicable si vous utilisez des implémentations externes vous ne maîtrisez pas.

EDIT:

Pour gérer le problème exact que vous avez besoin d'avoir un compteur qui ne comptent plus que la première instance.

mes 2 cents

Il n'y a aucun moyen d'interroger les sous-classes d'une classe dans (native) C ++. Comment créer les instances? Pensez à utiliser une méthode Factory vous permet de parcourir tous les sous-classes que vous travaillez. Lorsque vous créez une instance comme celui-ci, il ne sera pas possible d'oublier l'ajout d'une nouvelle sous-classe plus tard.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top