警告: この質問では、例として RPG との類似性を使用しています。
C# を使用して、私が夢見ていたこの RPG を作成しているとしましょう。プレイヤーが戦闘に入ると、戦場で利用可能なさまざまな対戦相手やアイテムなど、戦闘に関連するすべての要素への参照を保持する、ある種の戦場が表示されます。
現在、これらすべての要素には 1 つではなくいくつかの役割があります。たとえば、味方 (直接継承によるファイター) は、戦場を移動したり、コマンドを発行したり、敵の標的になったりすることができます。
今では、石の中の強力な剣も戦闘でいくつかの役割を果たしています. 明らかに、移動したりコマンドを発行したりすることはできませんが、それでもターゲットにすることができ、(うまくいけば)持ち上げたり使用したりできます.
これらの動作はすべて、コード内のインターフェイスによって表されるため、実装するオブジェクトに関係なく、同じ動作を持つすべてのオブジェクトを使用できます。
コード例:
public class Ally : Fighter, IMovable, IActor, ITarget
{
// IMovable implementation
public void Move(...) { ... }
// IActor implementation
public bool HasInitiative { get { ... } }
public ICommand IssueCommand(...) { ... }
// ITarget implementation
public void OnTarget(...) { ... }
// other code ...
}
public class MightySword : ITarget, ILiftable, IUsable
{
// ITarget implementation
public void OnTarget(...) { ... }
// ... other interface implementation
// other code ...
}
ここで私の質問は次のとおりです。Battlefield クラスは、これらすべてのオブジェクトへの参照をどのように保持する必要がありますか? 考えられる解決策を 2 つ思いつきましたが、現時点では、どちらが最適か、さらに優れた解決策が見つかるかどうかはわかりません。
ソリューション A:
複数回参照し、直接アクセスする: このソリューションを使用すると、各オブジェクトは実装するインターフェイスごとに 1 回参照されるため、私の戦場クラスは次のようになります。
public class Battlefield : ...
{
IList<IMovable> movables;
IList<ITarget> targets;
IList<IActor> actors;
// ... and so on
}
Battlefield.Update メソッドは次のようになります。
Update(...)
{
// Say every actor needs to target somebody else
// it has nothing to do in the Update method,
// but this is an example
foreach (IActor actor in actors)
{
if (actor.HasInitiative)
{
ITarget target = targets[actor.TargetIndex];
target.OnTarget(actor.IssueCommand(...));
}
}
}
このソリューションにより、オブジェクトのさまざまな動作に直接アクセスできますが、Ally を可動オブジェクト、ターゲット、およびアクターで参照する必要があるため、冗長性が生じます。この冗長性は、より大きなメモリフットプリントを意味し、バトルフィールドへのオブジェクトの追加と削除をより複雑にします (各タイプは、どのコレクションから追加/削除する必要があります)。
また、新しいインターフェイスを追加するということは、アイテムを保持し、対応するコードを管理するための新しいコレクションを追加することを意味します。
ソリューション B:
1 回の参照、フィルター アクセス: このソリューションを使用すると、すべてのオブジェクトが 1 つのクラスまたはインターフェイス (IBattleElement に似たもの) から派生し、1 つのコレクションに格納されます。要素を使用する場合、それらはそのタイプに従ってフィルタリングされます。
Update(...)
{
IList<IActor> actors = elements.OfType<Actor>();
foreach (IActor actor in actors)
{
ITarget target = elements.OfType<Target>()[actor.TargetIndex];
target.OnTarget(actor.IssueCommand(...));
}
}
そこには。これ以上の冗長性はありませんが、「typeof」構造を使用するのは嫌いで、通常は避けようとします。実行時のパフォーマンスに関しては、明らかにこれの方が悪いです。
おもちゃのプロジェクトでソリューション A を実装しましたが、このプロジェクトではパフォーマンスが重要になるものは何もありません。答えは、パフォーマンス (メモリに関しても CPU に関しても) だけでなく、BattleElement の新しいクラスや新しい動作のための新しいインターフェイスを追加するなど、私やデザイナーが実行しなければならない操作に最適な設計を提供するものも考慮する必要があります。バトルフィールドからオブジェクトのインスタンスを削除し、より一般的にはゲーム ロジックで使用します。
私の例が一般的にばかげていることをお詫びします。私の英語が十分であることを願っています。また、私の質問が少なくとも少しは興味を引くものであり、設計自体の全体的な愚かさを示していないことを願っています。