Как я могу отслеживать (перечислять) все классы, реализующие интерфейс?
Вопрос
У меня есть ситуация, когда у меня есть интерфейс, который определяет, как ведет себя определенный класс для выполнения определенной роли в моей программе, но на данный момент я не уверен на 100%, сколько классов я напишу для выполнения этой роли. .Однако в то же время я знаю, что хочу, чтобы пользователь мог выбрать из поля со списком/списком графического интерфейса конкретный класс, реализующий интерфейс, который он хочет использовать для выполнения определенной роли.Я хочу, чтобы графический интерфейс мог перечислять все доступные классы, но я бы предпочел не возвращаться и не менять старый код всякий раз, когда я решу реализовать новый класс для выполнения этой роли (что может произойти через несколько месяцев).
Некоторые вещи, которые я рассмотрел:
- используя перечисление
- Плюсы:
- Я знаю, как это сделать
- Минусы
- Мне придется обновить перечисление, когда я добавлю новый класс.
- некрасиво перебирать
- Плюсы:
- используя какой-то
static
список объектов в интерфейсе и добавление нового элемента из файла определения реализующего класса.- Плюсы:
- Не придется менять старый код
- Минусы:
- Даже не уверен, что это возможно
- Не уверен, какую информацию хранить, чтобы фабричный метод мог выбрать правильный конструктор (возможно, карту между строкой и указателем функции, которая возвращает указатель на объект интерфейса).
- Плюсы:
Я предполагаю, что это проблема (или подобная проблеме), с которой более опытные программисты, вероятно, сталкивались раньше (и часто), и, вероятно, существует общее решение такого рода проблем, которое почти наверняка лучше, чем все, что я я способен придумать.Итак, как мне это сделать?
(П.С.Я искал, но нашел только это, и это не то же самое: Как перечислить все элементы, реализующие общий интерфейс?.Похоже, он уже знает, как решить проблему, которую я пытаюсь выяснить.)
Редактировать:Я переименовал заголовок в «Как я могу отслеживать...", а не просто "Как я могу перечислить...", потому что исходный вопрос звучал так, будто меня больше интересовало изучение среды выполнения, а то, что меня действительно интересует, - это ведение бухгалтерского учета во время компиляции.
Решение
Создайте синглтон, в котором вы сможете зарегистрировать свои классы с помощью указателя на функцию-создатель.В файлах 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++ невозможно запросить подклассы класса.Как вы создаете экземпляры?Рассмотрите возможность использования фабричного метода, позволяющего перебирать все подклассы, с которыми вы работаете.Когда вы создадите такой экземпляр, вы не сможете забыть позже добавить новый подкласс.