不要害怕...
我猜你的问题在于熟悉度,而不是技术。熟悉 C++ OOP。
C++ 是一种面向对象语言
在其众多范式中,它具有 OOP 特性,并且能够支持与大多数纯 OO 语言的比较。
不要让“C++ 中的 C 部分”让您相信 C++ 无法处理其他范例。C++ 可以非常优雅地处理许多编程范式。其中,OOP C++是继过程范式(即前面提到的“C部分”)之后最成熟的C++范式。
多态性可以用于生产
没有“微妙的错误”或“不适合生产代码”的东西。有些开发人员保持自己的方式,有些开发人员将学习如何使用工具并为每项任务使用最好的工具。
开关和多态性[几乎]相似......
...但是多态性消除了大多数错误。
不同之处在于您必须手动处理开关,而多态性更自然,一旦您习惯了继承方法覆盖。
使用开关,您必须将类型变量与不同类型进行比较,并处理差异。使用多态性,变量本身知道如何表现。您只需要以逻辑方式组织变量,并覆盖正确的方法。
但最后,如果你忘记在 switch 中处理 case,编译器不会告诉你,而如果你从一个类派生而没有覆盖它的纯虚方法,你会被告知。因此,避免了大多数开关错误。
总而言之,这两个功能都是关于做出选择的。但是多态性使您能够做出更复杂的同时更自然的选择,从而更容易做出选择。
避免使用 RTTI 来查找对象的类型
RTTI 是一个有趣的概念,并且很有用。但大多数时候(即 95% 的时间),方法覆盖和继承就绰绰有余了,您的大部分代码甚至不应该知道所处理对象的确切类型,而是相信它会做正确的事情。
如果你使用 RTTI 作为一个美化的开关,你就错过了重点。
(免责声明:我是 RTTI 概念和 dynamic_casts 的忠实粉丝。但是必须为手头的任务使用正确的工具,而且大多数时候 RTTI 被用作美化开关,这是错误的)
比较动态与静态多态性
如果您的代码在编译时不知道对象的确切类型,则使用动态多态(即经典继承、虚拟方法覆盖等)
如果您的代码在编译时知道类型,那么也许您可以使用静态多态,即 CRTP 模式http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
CRTP 将使您拥有闻起来像动态多态性的代码,但其每个方法调用都将静态解析,这对于一些非常关键的代码来说是理想的。
生产代码示例
与此类似的代码(来自内存)用于生产。
更简单的解决方案围绕消息循环调用的过程(Win32 中的 WinProc,但为了简单起见,我编写了一个更简单的版本)。所以总结一下,它是这样的:
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 模式解决了这个问题,也就是说,使用一个 process() 方法创建一个基本的 Command 对象。
所以我重新编写了消息程序,将危险代码(即使用 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
}
} ;
正如我所说:易于理解(如果注释正确),并且非常易于扩展。
该开关被减少到最低限度(即 if-like,因为我们仍然需要将 Windows 命令委托给 Windows 默认过程),并且不需要 RTTI(或者更糟糕的是,内部 RTTI)。
我想,开关中的相同代码会很有趣(如果仅根据我在工作应用程序中看到的“历史”代码的数量来判断的话)。