Не бойся ...
Полагаю, ваша проблема в знакомстве, а не в технологиях. Ознакомьтесь с C ++ ООП.
C ++ является языком ООП
Среди множества парадигм он имеет функции ООП и более чем способен поддерживать сравнение с наиболее чистым языком ОО.
Не позволяйте "части C внутри C ++" заставить вас поверить, что C ++ не может иметь дело с другими парадигмами. C ++ может очень любезно работать с множеством парадигм программирования. И среди них ООП C ++ является наиболее зрелой из парадигм C ++ после процедурной парадигмы (то есть вышеупомянутой «части C»).
Полиморфизм в порядке для производства
Здесь нет "тонких ошибок" или "не подходит для производственного кода". Есть разработчики, которые остаются на своем пути, и разработчики, которые научатся использовать инструменты и использовать лучшие инструменты для каждой задачи.
переключатель и полиморфизм [почти] похожи ...
... Но полиморфизм убрал большинство ошибок.
Разница в том, что вы должны обрабатывать переключатели вручную, тогда как полиморфизм более естественен, как только вы привыкнете к переопределению метода наследования.
С помощью переключателей вам придется сравнивать переменные типа с разными типами и обрабатывать различия. При полиморфизме сама переменная знает, как себя вести. Вам нужно только упорядочить переменные логическими способами и переопределить правильные методы.
Но, в конце концов, если вы забудете обработать случай в switch, компилятор не скажет вам, а вам сообщат, если вы наследуете класс, не переопределяя его чисто виртуальные методы. Таким образом, большинство ошибок переключения исключаются.
В общем, две функции о выборе. Но полиморфизм позволяет вам сделать более сложным и в то же время более естественным и, следовательно, более легким выбором.
Избегайте использования RTTI для поиска типа объекта
RTTI - интересная концепция и может быть полезной. Но в большинстве случаев (то есть в 95% случаев) переопределения и наследования методов будет более чем достаточно, и большая часть вашего кода даже не должна знать точный тип обрабатываемого объекта, но доверять ему, чтобы он поступал правильно.
Если вы используете RTTI в качестве прославленного коммутатора, вы упускаете суть.
(Отказ от ответственности: я большой поклонник концепции RTTI и dynamic_casts. Но для этой задачи нужно использовать правильный инструмент, и большую часть времени RTTI используется в качестве прославленного переключателя, что неправильно)
Сравнение динамического и статического полиморфизма
Если ваш код не знает точный тип объекта во время компиляции, тогда используйте динамический полиморфизм (то есть классическое наследование, переопределение виртуальных методов и т. Д.)
Если ваш код знает тип во время компиляции, то, возможно, вы могли бы использовать статический полиморфизм, то есть шаблон CRTP http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
CRTP позволит вам иметь код, который пахнет как динамический полиморфизм, но каждый вызов метода будет разрешаться статически, что идеально для некоторого очень важного кода.
Пример кода продукции
Код, аналогичный этому (из памяти), используется в производстве.
Более простое решение основывалось на процедуре, вызываемой циклом сообщений (WinProc в Win32, но для простоты я написал более простую версию). Итак, подведем итог, это было что-то вроде:
void MyProcedure(int p_iCommand, void *p_vParam)
{
// A LOT OF CODE ???
// each case has a lot of code, with both similarities
// and differences, and of course, casting p_vParam
// into something, depending on hoping no one
// did a mistake, associating the wrong command with
// the wrong data type in p_vParam
switch(p_iCommand)
{
case COMMAND_AAA: { /* A LOT OF CODE (see above) */ } break ;
case COMMAND_BBB: { /* A LOT OF CODE (see above) */ } break ;
// etc.
case COMMAND_XXX: { /* A LOT OF CODE (see above) */ } break ;
case COMMAND_ZZZ: { /* A LOT OF CODE (see above) */ } break ;
default: { /* call default procedure */} break ;
}
}
Каждое добавление команды добавляет регистр.
Проблема в том, что некоторые команды были похожи и частично разделяли их реализацию.
Таким образом, смешение дел было риском для эволюции.
Я решил проблему с помощью шаблона Command, то есть создания базового объекта Command с одним методом process ().
Поэтому я переписал процедуру сообщения, сводя к минимуму опасный код (т. Е. Играя с void * и т. Д.), И написал его, чтобы быть уверенным, что мне больше не нужно будет его трогать:
void MyProcedure(int p_iCommand, void *p_vParam)
{
switch(p_iCommand)
{
// Only one case. Isn't it cool?
case COMMAND:
{
Command * c = static_cast<Command *>(p_vParam) ;
c->process() ;
}
break ;
default: { /* call default procedure */} break ;
}
}
И затем для каждой возможной команды вместо добавления кода в процедуру и смешивания (или, что еще хуже, копирования / вставки) кода из аналогичных команд я создал новую команду и извлек ее либо из объекта Command, или один из его производных объектов:
Это привело к иерархии (представленной в виде дерева):
[+] Command
|
+--[+] CommandServer
| |
| +--[+] CommandServerInitialize
| |
| +--[+] CommandServerInsert
| |
| +--[+] CommandServerUpdate
| |
| +--[+] CommandServerDelete
|
+--[+] CommandAction
| |
| +--[+] CommandActionStart
| |
| +--[+] CommandActionPause
| |
| +--[+] CommandActionEnd
|
+--[+] CommandMessage
Теперь все, что мне нужно было сделать, это переопределить процесс для каждого объекта.
Простой и легко расширяемый.
Например, скажем, что CommandAction должен был выполнить свой процесс в три этапа: «до», «пока» и «после». Его код будет выглядеть примерно так:
class CommandAction : public Command
{
// etc.
virtual void process() // overriding Command::process pure virtual method
{
this->processBefore() ;
this->processWhile() ;
this->processAfter() ;
}
virtual void processBefore() = 0 ; // To be overriden
virtual void processWhile()
{
// Do something common for all CommandAction objects
}
virtual void processAfter() = 0 ; // To be overriden
} ;
И, например, CommandActionStart можно кодировать как:
class CommandActionStart : public CommandAction
{
// etc.
virtual void processBefore()
{
// Do something common for all CommandActionStart objects
}
virtual void processAfter()
{
// Do something common for all CommandActionStart objects
}
} ;
Как я уже сказал: легко понять (если правильно прокомментировать) и очень легко расширить.
Переключатель сведен к минимуму (т. Е. Если подобен, потому что нам все еще нужно было делегировать команды Windows в процедуру Windows по умолчанию), и нет необходимости в RTTI (или, что хуже, в собственном RTTI).
Один и тот же код внутри коммутатора был бы довольно забавным, я думаю (если бы только судя по количеству «исторического» кода, который я видел в нашем приложении на работе).