Como posso acompanhar (Enumerar) todas as classes que implementam uma interface

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

  •  12-09-2019
  •  | 
  •  

Pergunta

Eu tenho uma situação onde eu tenho uma interface que define como um certas comporta classe, a fim de preencher um certo papel no meu programa, mas neste momento eu não estou 100% certo quantas aulas eu vou escrever para preencher esse papel. No entanto, ao mesmo tempo, eu sei que eu quero que o usuário seja capaz de selecionar, a partir de uma caixa de GUI combo / lista, que classe concreta que implementa a interface que deseja usar para preencher um certo papel. Eu quero que o GUI para ser capaz de enumerar todas as classes disponíveis, mas eu preferiria não ter que voltar atrás e mudar o código antigo sempre que eu decidir implementar uma nova classe para preencher esse papel (que pode ser de meses a partir de agora)

Algumas coisas eu considerei:

  1. usando uma enumeração
    • Prós:
      1. Eu sei como fazê-lo
    • Contras
      1. terei de atualização atualização a enumeração quando eu adicionar uma nova classe
      2. feio para percorrer
  2. usando algum tipo de lista static objeto na interface, e adicionando um novo elemento de dentro do arquivo de definição da classe implementando
    • Prós:
      1. não terá que mudar o código antigo
    • Contras:
      1. nem tenho certeza se isso é possível
      2. Não tenho certeza que tipo de informação para armazenar de modo que um método de fábrica pode escolher o construtor apropriado (talvez um mapa entre uma corda e um ponteiro de função que retorna um ponteiro para um objeto da interface)

Eu estou supondo que este é um problema (ou semelhante a um problema) que os programadores mais experientes provavelmente se deparar antes (e muitas vezes), e provavelmente há uma solução comum para este tipo de problema, que é quase certamente melhor do que qualquer coisa que eu sou capaz de chegar com. Então, como eu faço isso?

(PS Eu procurei, mas tudo o que eu encontrei foi isto, e não é o mesmo: como posso enumerar todos os itens que implementam uma interface genérica? . parece que ele já sabe como resolver o problema que estou tentando descobrir.)

Edit: eu renomeado o título de "Como posso manter o controle de ..." em vez de apenas "Como posso enumerar ..." porque a pergunta original parecia que eu estava mais interessado em examinar o ambiente de execução, onde como o que eu estou realmente interessado em é em tempo de compilação contabilidade.

Foi útil?

Solução

Criar um singleton onde você pode registrar suas aulas com um ponteiro para uma função de criador. Nos arquivos CPP das classes concretas que você registrar cada classe.
Algo parecido com isto:

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

Outras dicas

Eu me lembro vagamente fazendo algo semelhante a isto há muitos anos. Sua opção (2) é muito bonito o que eu fiz. Nesse caso, foi uma std::map de std::string para std::typeinfo. Em cada um, arquivo.cpp I registrou a classe como este:

static dummy = registerClass (typeid (MyNewClass));

registerClass leva um objeto type_info e simplesmente retorna true. Você tem que inicializar uma variável para garantir que registerClass é chamado durante o tempo de inicialização. Simplesmente chamando registerClass no espaço global é um erro. E fazendo dummy estática permitem reutilizar o nome em unidades de compilação sem uma colisão nome.

Eu referida neste artigo para implementar uma fábrica de classe de auto-registro semelhante ao descrito na resposta de TimW, mas tem o truque agradável de usar uma classe de proxy fábrica templated para lidar com o registro objeto. Vale a pena olhar:)

Auto-registro de objetos em C ++ -> http://www.ddj.com/184410633

Editar

Aqui está o aplicativo de teste que eu fiz (arrumado um pouco;):

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

saída:

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

Se você estiver no Windows, e usando C ++ / CLI, isso se torna bastante fácil. O .NET framework fornece essa capacidade através da reflexão, e ele funciona muito limpa em código gerenciado.

Em C ++ nativo, este recebe um complicado bit pouco, como não há nenhuma maneira simples para consultar a biblioteca ou pedido de informações de tempo de execução. Há muitos estruturas que fornecem este (basta olhar para IoC, DI, ou estruturas plugin), mas os meios mais simples de fazê-lo sozinho é ter algum tipo de configuração que um método de fábrica pode usar para registrar-se, e retornar uma implementação de sua classe base específica. Você tinha acabado precisa implementar o carregamento de uma DLL, e registrar o método de fábrica -. Uma vez que você tem isso, é bastante fácil

Algo que você pode considerar é um contador de objeto. Desta forma, você não precisa mudar a cada lugar que você alocar mas a definição implementação justa. É uma alternativa para a solução de fábrica. Considere vantagens / desvantagens.

Uma maneira elegante de fazer isso é usar o CRTP: Curiosamente recorrentes template padrão . O principal exemplo é essa contra um:)

Desta forma, você só tem que adicionar em sua implementação classe concreta:

class X; // your interface

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

É claro, não é aplicável se você usar implementações externas você não dominar.

EDIT:

Para lidar com o problema exato que você precisa ter um contador que conta apenas a primeira instância.

meus 2 centavos

Não há nenhuma maneira de consultar as subclasses de uma classe em C (nativo) ++. Como você cria as instâncias? Considere o uso de um método de fábrica que lhe permite interagir sobre todas as subclasses que você está trabalhando. Quando você cria uma instância como esta, não será possível esquecer de adicionar uma nova subclasse mais tarde.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top