恐れないで...
あなたの問題はテクノロジーではなく、親しみやすさにあると思います。C++ OOP に慣れてください。
C++ は OOP 言語です
その複数のパラダイムの中で、OOP 機能があり、ほとんどの純粋な OO 言語との比較をサポートする以上の能力があります。
「C++ 内の C 部分」という理由で、C++ が他のパラダイムを処理できないと信じ込ませないでください。C++ は、多くのプログラミング パラダイムを非常に優雅に処理できます。その中でも、OOP C++ は、手続き型パラダイム (つまり、前述の「C 部分」) に次いで最も成熟した C++ パラダイムです。
ポリモーフィズムは本番環境で問題ありません
「微妙なバグ」や「製品コードに適していない」ことはありません。自分のやり方に固執する開発者もいれば、ツールの使い方を学び、各タスクに最適なツールを使用する開発者もいます。
スイッチとポリモーフィズムは[ほぼ]似ています...
...しかし、ポリモーフィズムはほとんどのエラーを取り除きました。
違いは、スイッチを手動で処理する必要があることです。一方、ポリモーフィズムは、継承メソッドのオーバーライドに慣れるとより自然になります。
スイッチを使用すると、型変数を異なる型と比較し、違いを処理する必要があります。ポリモーフィズムでは、変数自体がどのように動作するかを知っています。論理的な方法で変数を整理し、適切なメソッドをオーバーライドするだけで済みます。
しかし、最終的に、switch でケースを処理するのを忘れた場合、コンパイラは通知しませんが、純粋仮想メソッドをオーバーライドせずにクラスから派生した場合は通知されます。したがって、ほとんどのスイッチ エラーは回避されます。
全体として、2 つの機能は選択に関するものです。しかし、ポリモーフィズムを使用すると、より複雑にすると同時に、より自然で簡単な選択を行うことができます。
オブジェクトのタイプを見つけるために 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 パターンを使用して問題を解決しました。つまり、1 つの 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 が「before」、「while」、「after」の 3 つのフェーズでプロセスを実行することになっているとします。そのコードは次のようになります。
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 のように、Windows コマンドを Windows の既定の手順に委譲する必要があったため)、RTTI (またはさらに悪いことに、社内 RTTI) は必要ありません。
スイッチ内の同じコードは、非常に面白いと思います (私が作業中のアプリで見た「歴史的な」コードの量から判断するだけなら)。