Здравствуйте!
Вопрос опять по архитектуре, но так как всё про плюсы, напишу сюда.
Вот у меня есть какая-то своя библиотека контролов.
У контролов есть два основных аспекта: реакция на пользовательский ввод и отрисовка. Реакцию на пользовательский ввод я пока выделять особо не хочу, пусть будет зашита, а вот отрисовку — хочу выделить отдельно.
Цель простая — дать пользователю возможность создавать свои "темы", без необходимости ковыряться в кишках либы или делать своих наследников и переопределять им методы рисования.
Во всех случаях у нас есть какая-то IControlPainterFactory, которая один раз в начале задаётся. А вот далее возникают вопросы, как лучше сделать
Какие я вижу альтернативы.
1) У нас есть
IControlPainter IControlPainterFactory::getPainter(IControl *pControl)
Там dynamic_cast'ами определять, для какого конкретно типа вызван getPainter, и возвращать соответствующую реализацию.
Или, можно сделать
enum ControlType {PushButton, RadioButton, CheckBox, ListView, TrieView, ...}
и
IControlPainter IControlPainterFactory::getPainter(ControlType controlType)
Это вроде как будет чутка пошустрее и более бинарно совместимо, если я захожу рисовальщик темы выделить в отдельную DLL, и собрать её другим компилятором.
Вопрос такой — как быть в том случае, если пользователь создал свой тип контрола, и сопоставил ему какой-то свой ControlType, дополняющий исходное определение? Тут будут грязные касты, насколько это фу? И, кстати, а как поведут себя плюсовые компиляторы различных стандартов, если будет что-то такое:
const int MyCoolControlType = 1111;
//...
case (ControlType)MyCoolControlType:
//...
А то последнее время гайки с enum'ами в switch стали подзакручивать. Хотя, можно и без switch обойтись, варианты есть.
2) Собственно, сам IControlPainter.
2.а Можно сделать
void IControlPainter::paintControl(ICanvas *pCanvas, IControl *pControl)
И пусть там из pControl динамик кастят в нужный тип контрола, извлекают из него всю необходимую для отрисовки инфу, и рисуют. Но опять же, это, как минимум, не очень бинарно совместимо. Но, тут можно в одном типе пайнтера сделать отрисовку всех контролов
2.б Можно сделать каст в хосте
IControlPainter* pBasePainter = pControlPainterFactory->getPainter(...)
IPushButtonPainter* pPainter = dynamic_cast<IPushButtonPainter>(pBasePainter);
pPainter->paintPushButton(pCanvas, this);
Тут большой минус в том, что хост должен знать обо всех типах контролов и пайнтеров для них
2.в
Можно сделать с одним интерфейсом IControlPainter, а тип и состояние передавать enum/int'ами
enum ControlStatesPushButton{ Pushed, Unpushed };
enum ControlStatesCheckBox{ Checked, Unchecked };
enum ControlStatesCheckBox3State{ Checked, Unchecked, Third };
void IControlPainter::paintControl(ICanvas *pCanvas, IControl *pControl, ControlType controlType, int controlState)
Тут уже суровый кастинг в int. Хотя, в принципе: I) все эти состояния кнопок можно объединить в один enum с тремя состояниями, в ButtonBase сделать флаг — она 2State или 3State, но это просто недостаток моего примера; II) Отдельно можно не передавать int controlState, а просто у базового контрола будет метод int getControlState() — но это те же яйца, только в профиль
Но данный вариант позволяет, как и (а) сделать отрисовку разных типов контролов в одном типе пайнтера.
Какие ещё недостатки есть в описанных мной сценариях?
Какие ещё есть варианты реализации подобного, с учетом того, что: а) хотелось бы получить бинарную совместимость между разными компиляторами; б) я буду это запихивать в скрипт-движок, и он близок скорее к сишечке; в) я хочу дать пользователю возможность самому делать ссвои контролы как в отдельных DLL, так и в скрипте