プリアンブル: C++でシンプルなボードゲームを作成しています。このボードゲームでは、AIキャラクターがボード上の四角形を移動します。正方形にはさまざまなタイプがあり、それぞれが抽象クラスから継承され、それぞれがAIキャラクターに異なる影響を与えます。たとえば、キャラクターが商品を購入できるショップや、キャラクターがお金を稼ぐ職場などがあります。
同様に、さまざまな種類の決定を行うさまざまなAIキャラクター(抽象クラスから継承)があります。一部の人は他の人よりも仕事が多い、一部の人は他の人よりも多く食べる、などです。
問題は、AIキャラクターが何をすべきかについて決定を下すために、正方形のタイプを知る必要があることです。現時点では、タイプ名文字列をメンバー変数として使用してタイプをチェックするのが最も簡単な解決策ですが、新しいタイプの四角形とAI文字を追加する場合、それはひどく洗練されておらず、あまり役に立ちません。
質問:この目的のためにクラスに配置される識別子を要求することによってオブジェクトのタイプを確認することは悪い習慣ですか?実行のフローを制御する目的で、dynamic_castやtypeinfoなどのランタイム型識別の他のメソッドを使用することは悪い習慣ですか?
例: Squareクラスの下(currentSquare()からアクセス)には、型の名前を含む文字列を返すメンバー関数getType()があります。
//Normal AI character
void decide() {
if(currentSquare()->getType()=="HOME") {
watchTV();
sleep();
}
if(currentSquare()->getType()=="WORKPLACE") {
work();
}
}
//Party animal AI
void decide() {
if(currentSquare()->getType()=="HOME") {
moveTo(getGameBoard()->closestBar());
}
if(currentSquare()->getType()=="WORKPLACE") {
sleep();
}
}
Amonが指摘したように、これは 訪問者パターン の良いアプリケーションです。これを使用すると、AIクラスは次のようになります。
void decide(HomeSquare square);
void decide(WorkSquare square);
void decide(ShopSquare square);
そしてあなたの正方形には次のようなaccept
関数があります:
void accept(AI ai)
{
ai.decide(this);
}
これにより、継承を使用して適切なデフォルトを作成したり、すべてのAIに特定の決定を実装させたりすることができます。たとえば、大量のコードをコピーせずに、自宅以外ではまったく同じように動作するAIを作成できます。コンパイラは、何かを忘れた場所を指摘するので、あちこちにキャストする必要はありません。
オブジェクトのクラスを識別するために文字列を使用することの脆弱性を指摘したいと思います。型の安全性にある程度取り組んでいますが、何か問題が発生すると、実行時に奇妙なエラーが発生する可能性があります。かなりの数のクラスがある場合、確認したいのですが、新しいクラスを導入したり、別のクラスを削除したりすると、コードのすべての部分を更新するのが面倒になる可能性があります。さらに、静的分析を複雑にし、場合によっては防止し、自動的にバグ(デッドコードなど)を見つける可能性を減らします。
これを、他の人がすでに指摘したビジターパターンを使用する場合と比較してください。タイプセーフになり、(何か問題が発生した場合は)コンパイル時エラーが発生します。これに加えて、訪問者パターンにより柔軟性が増し、カスケードされたif-elseブロックやスイッチブロックが長くカスケードされることがなくなり、可読性と保守性が向上します。静的分析ツールは意図したとおりに機能します。
そのため、メンバー変数を識別に使用することは、最高の準最適であると主張します。
完全な開示:私は主にJava開発者ですが、この場合Javaとc ++の違いは問題ではないと思います。私が間違っている場合私を訂正してください。;)
このような状況では、文字列ではなく列挙型を使用することを好みます。「チェックタイプ」コードは、不特定のケースを除外するように記述する必要があります。
switch (currentSquare()->getType())
{ default: BUG("case not expected");
case workplace: ...
break;
case home: ..
break;
}