Я в настоящее время на этапе разработки библиотеки классов и наткнулся на вопрос, похожий на «Управление различными классами с центральным менеджером без RTTI» или же «шаблон, чтобы избежать dynamic_cast».
Представьте, что есть иерархия классов с базовым классом База и два класса DerivedA а также DerivedB которые являются подклассами База. Где-то в моей библиотеке будет класс, который должен содержать списки объектов обоих типов DerivedA а также DerivedB. Далее предположим, что этот класс должен будет выполнить действия для обоих типов в зависимости от типа. Очевидно, что здесь я буду использовать виртуальные функции для реализации этого поведения. Но что, если мне понадобится управляющий класс, чтобы дать мне все объекты типа DerivedA?
Является ли это индикатором плохого дизайна класса, потому что мне нужно выполнять действия только над подмножеством иерархии классов?
Или это просто означает, что мой управляющий класс не должен использовать список База но два списка — один для DerivedA и один для DerivedB? Так что в случае, если мне нужно выполнить действие с обоими типами, мне нужно будет перебрать два списка. В моем случае вероятность того, что возникнет необходимость добавить новые подклассы в иерархию, достаточно мала, и текущее число составляет около 3 или 4 подклассов.
Но что, если мне понадобится управляющий класс, чтобы дать мне все объекты
Тип DerivedA?Является ли это индикатором плохого дизайна класса, потому что у меня есть необходимость
выполнять действия только над подмножеством иерархии классов?
Скорее да, чем нет. Если вам часто нужно это делать, то имеет смысл задать вопрос, имеет ли смысл иерархия. В этом случае вы должны разделить это на два несвязанных списка.
Другой возможный подход состоит в том, чтобы также обрабатывать его с помощью виртуальных методов, например, DeriveB
будет иметь неоперативную реализацию для методов, которые не влияют на это. Трудно сказать, не зная больше информации.
Это, безусловно, признак плохого дизайна, если вы храните (указатели) объекты вместе, которые должны обрабатываться по-разному.
Однако вы можете просто реализовать это отличающееся поведение как пустую функцию в базовом классе или использовать шаблон посетителя.
Вы можете сделать это несколькими способами.
dynamic_cast
к конкретному классу (это грубое решение, но я бы использовал его только для интерфейсов, использование его для классов — это своего рода запах кода. Хотя это будет работать.)Сделать что-то вроде:
class BaseRequest {};
class DerivedASupportedRequest : public BaseRequest {};
Затем измените ваши классы для поддержки метода:
// (...)
void ProcessRequest(const BaseRequest & request);
Создать виртуальный метод bool TryDoSth()
в базовом классе; DerivedB
всегда будет возвращать ложь, в то время как DerivedA
будет реализовывать необходимые функции.
Supports(Action action)
, где Action
перечисление, определяющее возможные действия или группы действий; в таком случае зовет DoSth()
в классе, который не поддерживает данную функцию, должно вызывать исключение.ActionXController * GetControllerForX()
; DerivedA
вернет фактический контроллер, DerivedB
вернусь nullptr
,BaseController * GetController(Action a)
Вы спросили, если это плохой дизайн. Я считаю, что это зависит от того, сколько функциональности является общим и сколько отличается. Если у вас есть 100 общих методов и только один другой, было бы странно хранить эти данные в отдельных списках. Однако, если количество различных методов заметно, рассмотрите возможность изменения дизайна вашего приложения. Это может быть общим правилом, но есть и исключения. Трудно сказать, не зная контекста.