У меня есть вопрос дизайна, который беспокоил меня некоторое время, но я не могу найти хорошее (в смысле ООП) решение для этого. Язык — C++, и я постоянно возвращаюсь к RTTI, который часто называют индикатором плохого дизайна.
Предположим, у нас есть набор различных типов модулей, реализованных в виде разных классов. Каждый тип модуля характеризуется определенным интерфейсом, однако реализация может различаться. Таким образом, моей первой идеей было создать класс интерфейса (чисто абстрактный) для каждого типа модуля (например, IModuleFoo, IModuleBar и т. д.) и реализации в отдельных классах. Все идет нормально.
class IModuleFoo {
public:
virtual void doFoo() = 0;
};
class IModuleBar {
public:
virtual void doBar() = 0;
};
С другой стороны, у нас есть набор классов (приложений), и каждый из них использует пару этих модулей, но только через интерфейсы — даже сами модули могут использовать другие модули. Однако все классы приложений будут использовать один и тот же пул модулей. Моя идея состояла в том, чтобы создать класс менеджера (ModuleManager) для всех модулей, которые классы приложений могут запрашивать для необходимых им типов модулей. Доступные модули (и конкретная реализация) настраиваются во время инициализации менеджера и могут меняться со временем (но это не является частью моего вопроса).
Поскольку количество различных типов модулей, скорее всего, > 10 и может увеличиваться со временем, мне не кажется подходящим хранить ссылки (или указатели) на них отдельно. Кроме того, может быть несколько функций, которые менеджер должен вызывать для всех управляемых модулей. Таким образом, я создал еще один интерфейс (IManagedModule) с тем преимуществом, что теперь я могу использовать контейнер (список, набор и т. д.) IManagedModules для их хранения в диспетчере.
class IManagedModule {
public:
virtual void connect() = 0;
{ ... }
};
Следствием этого является то, что управляемый модуль должен наследовать как от IManagedModule, так и от соответствующего интерфейса для его типа.
Но все становится ужасно, когда я думаю о ModuleManager. Можно предположить, что в каждый момент времени присутствует не более одного экземпляра каждого типа модуля. Таким образом, если бы можно было сделать что-то подобное (где менеджер — это экземпляр ModuleManager), все было бы в порядке:
IModuleFoo* pFoo = manager.get(IModuleFoo);
Но я почти уверен, что это не так. Я также подумал о решении на основе шаблона, например:
IModuleFoo* pFoo = manager.get<IModuleFoo>();
Это могло бы сработать, но я понятия не имею, как найти нужный модуль в менеджере, если все, что у меня есть, это набор IManagedModules — то есть без использования RTTI, конечно.
Один из подходов состоит в том, чтобы предоставить IManagedModule виртуальный метод getId(), полагаться на реализации, чтобы использовать недвусмысленные идентификаторы для каждого типа модуля и выполнять приведение указателя самостоятельно. Но это просто изобретение колеса (а именно RTTI) и требует большой дисциплины в классах реализации (предоставление правильных идентификаторов и т. д.), что нежелательно.
Короче говоря, вопрос заключается в том, действительно ли здесь нет способа обойти какой-либо RTTI, и в этом случае RTTI может быть даже допустимым решением, или может быть лучший (чище, безопаснее, ...) дизайн, который демонстрирует такая же гибкость (например, слабая связь между классами приложений и классами модулей...)? Я что-то пропустил?