Метод
ContentUIElement::SetContent у тебя сейчас реализует прямой контроль. Код работает с переданным контекстом и указывает ему что-то делать.
Загвоздка у тебя в том, что действия кода полагаются на типизацию элементов контекста. В этом случае люди часто принимают чисто ошибочное решение делать проверку типа и действовать исходя из этой проверки.
Такой подход мог бы подойти для питона или JS, но не на плюсах, где он является источником ошибок.
В контексте метода
ContentUIElement::SetContent полностью все объекты, включая и объект по указателю
this имеют два типа:
статический тип и
динамический тип. Оба типа могут быть одинаковыми, но нередко они разные.
Игры со сменой статического типа в рантайме чреваты проблемами и гроздьями условий обхода этих пробелм.
Что же тогда надо делать? На помощь приходит виртуальный интерфейс и инверсия контроля.
Тебе поможет
шаблон визитера. Описание у Фаулера, конечно, хтоническое, но суть передает хорошо. А понять этот шаблон довольно просто на твоем же примере.
Что тебе требуется сделать? Если
content имеет один динамический тип, то выполнить
А. А если content имеет другой динамический тип, то выполнить
Б. В иных же случаях выполнить общее поведение.
Пусть у
IData появится еще один метод:
void SetupContentElement( ContentUIElement& element )
Пусть по умолчанию этот метод делает то, что находится в ветке
else метода
ContentUIElement::SetContent, а реализации для
UIElement и
TextContent выполняют свой код настройки самостоятельно.
В этом случае код
ContentUIElement::SetContent сводится к вызову
content->SetupContentElement( *this );.
При этом код получает эффект легкой расширяемости. Где сегодня два частных случая, там завтра будет уже три или пять. Еще одним важным эффектом для кода будет малое знание. Коду
ContentUIElement больше не надо знать о типах
UIElement и
TextContent.
Если есть какие-то проблемы с доступностью интерфейсов или семантикой вносимых изменений, эти все проблемы совсем не сложно убрать выстроив отдельную систему типов строго под задачу посещения.
Если не нравится вносить в
IData определенный виртуальный метод, делая его абстрактным классом, а не интерфейсом, то решение простое: плюс один шаг базового типа от
IData, в котором и определено общее поведение, и наследоваться уже от этого типа дальше, а не от
IData напрямую.