Как я могу отслеживать (перечислять) все классы, реализующие интерфейс?

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

  •  12-09-2019
  •  | 
  •  

Вопрос

У меня есть ситуация, когда у меня есть интерфейс, который определяет, как ведет себя определенный класс для выполнения определенной роли в моей программе, но на данный момент я не уверен на 100%, сколько классов я напишу для выполнения этой роли. .Однако в то же время я знаю, что хочу, чтобы пользователь мог выбрать из поля со списком/списком графического интерфейса конкретный класс, реализующий интерфейс, который он хочет использовать для выполнения определенной роли.Я хочу, чтобы графический интерфейс мог перечислять все доступные классы, но я бы предпочел не возвращаться и не менять старый код всякий раз, когда я решу реализовать новый класс для выполнения этой роли (что может произойти через несколько месяцев).

Некоторые вещи, которые я рассмотрел:

  1. используя перечисление
    • Плюсы:
      1. Я знаю, как это сделать
    • Минусы
      1. Мне придется обновить перечисление, когда я добавлю новый класс.
      2. некрасиво перебирать
  2. используя какой-то static список объектов в интерфейсе и добавление нового элемента из файла определения реализующего класса.
    • Плюсы:
      1. Не придется менять старый код
    • Минусы:
      1. Даже не уверен, что это возможно
      2. Не уверен, какую информацию хранить, чтобы фабричный метод мог выбрать правильный конструктор (возможно, карту между строкой и указателем функции, которая возвращает указатель на объект интерфейса).

Я предполагаю, что это проблема (или подобная проблеме), с которой более опытные программисты, вероятно, сталкивались раньше (и часто), и, вероятно, существует общее решение такого рода проблем, которое почти наверняка лучше, чем все, что я я способен придумать.Итак, как мне это сделать?

(П.С.Я искал, но нашел только это, и это не то же самое: Как перечислить все элементы, реализующие общий интерфейс?.Похоже, он уже знает, как решить проблему, которую я пытаюсь выяснить.)

Редактировать:Я переименовал заголовок в «Как я могу отслеживать...", а не просто "Как я могу перечислить...", потому что исходный вопрос звучал так, будто меня больше интересовало изучение среды выполнения, а то, что меня действительно интересует, - это ведение бухгалтерского учета во время компиляции.

Это было полезно?

Решение

Создайте синглтон, в котором вы сможете зарегистрировать свои классы с помощью указателя на функцию-создатель.В файлах cpp конкретных классов вы регистрируете каждый класс.
Что-то вроде этого:

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

Другие советы

Я смутно помню, как делал что-то подобное много лет назад.Ваш вариант (2) во многом похож на то, что я сделал.В том случае это был std::map из std::string к std::typeinfo.В каждом файле .cpp я зарегистрировал класс следующим образом:

static dummy = registerClass (typeid (MyNewClass));

registerClass занимает type_info объект и просто возвращает true.Вам необходимо инициализировать переменную, чтобы гарантировать, что registerClass вызывается во время запуска.Просто звоню registerClass в глобальном пространстве имен является ошибкой.и делая dummy static позволяют повторно использовать имя в модулях компиляции без конфликта имен.

Я обратился к этой статье, чтобы реализовать саморегистрирующуюся фабрику классов, аналогичную той, которая описана в ответе TimW, но в ней есть приятная хитрость: использование шаблонного фабричного прокси-класса для обработки регистрации объекта.Ну стоит посмотреть :)

Саморегистрация объектов в C++ -> http://www.ddj.com/184410633

Редактировать

Вот тестовое приложение, которое я сделал (немного прибрал;):

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

объекты.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"; }
};

объекты.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;
}

выход:

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

Если вы работаете в Windows и используете C++/CLI, это становится довольно легко.Платформа .NET предоставляет эту возможность посредством отражения и очень четко работает в управляемом коде.

В родном C++ это становится немного сложнее, поскольку не существует простого способа запросить у библиотеки или приложения информацию о времени выполнения.Есть много структуры, которые обеспечивают это (просто найдите платформы IoC, DI или плагинов), но самый простой способ сделать это самостоятельно — это иметь некоторую форму конфигурации, которую фабричный метод может использовать для регистрации и возврата реализации вашего конкретного базового класса.Вам просто нужно будет реализовать загрузку DLL и зарегистрировать фабричный метод - как только это у вас будет, это довольно просто.

Что-то, что вы можете рассмотреть, — это счетчик объектов.Таким образом, вам не нужно менять каждое выделенное место, а только определение реализации.Это альтернатива заводскому решению.Рассмотрим плюсы/минусы.

Элегантный способ сделать это — использовать ЦРТП:Любопытно повторяющийся шаблон шаблона.Основной пример — вот такой счетчик :)

Таким образом, вам просто нужно добавить в конкретную реализацию класса:

class X; // your interface

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

Конечно, это неприменимо, если вы используете внешние реализации, которыми не владеете.

РЕДАКТИРОВАТЬ:

Чтобы справиться с конкретной проблемой, вам нужен счетчик, который учитывает только первый экземпляр.

мои 2 цента

В (собственном) C++ невозможно запросить подклассы класса.Как вы создаете экземпляры?Рассмотрите возможность использования фабричного метода, позволяющего перебирать все подклассы, с которыми вы работаете.Когда вы создадите такой экземпляр, вы не сможете забыть позже добавить новый подкласс.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top