署名が同一の2つのインターフェース


13

カードに2つの重要な機能セットがあるカードゲームをモデル化しようとしています。

最初は効果です。これらは、カードをプレイしたときに起こるゲームの状態の変化です。有効なインターフェイスは次のとおりです。

boolean isPlayable(Player p, GameState gs);
void play(Player p, GameState gs);

そして、あなたはそのコストをまかなうことができ、そのすべての効果がプレイ可能である場合にのみ、カードをプレイ可能と考えることができます。そのようです:

// in Card class
boolean isPlayable(Player p, GameState gs) {
    if(p.resource < this.cost) return false;
    for(Effect e : this.effects) {
        if(!e.isPlayable(p,gs)) return false;
    }
    return true;
}

さて、これまでのところ、非常に簡単です。

カードの他の機能セットは能力です。これらの能力は、ゲームの状態の変化であり、自由にアクティブ化できます。これらのインターフェイスを思いついたとき、それらをアクティブ化できるかどうかを判断する方法と、アクティブ化を実装する方法が必要であることに気付きました。最終的には

boolean isActivatable(Player p, GameState gs);
void activate(Player p, GameState gs);

そして、「play」ではなく「activate」と呼ぶことを除いて、まったく同じ署名AbilityEffect持っていることに気付きました。


同一のシグネチャを持つ複数のインターフェースを持つのは悪いことですか?単純に1つを使用し、同じインターフェイスを2セット持つ必要がありますか?そのように:

Set<Effect> effects;
Set<Effect> abilities;

もしそうなら、どのようなリファクタリング(より多くの機能がリリースされるように)それらが同一ではなくなった場合、特に分岐している場合(つまり、どちらも一方が獲得するのではなく、他方が獲得すべきでないものを獲得する場合)手順を踏むべきですかそしてもう一方は完全なサブセットです)?特に、何かを変更するとすぐにそれらを組み合わせることが持続不可能になることを心配しています。

細かい活字:

この質問はゲーム開発によって生じたものであると認識していますが、ゲーム以外の開発でも、特に1つのアプリケーションで複数のクライアントのビジネスモデルに対応しようとすると、簡単に忍び寄ることができるような問題だと感じています私がこれまでに複数のビジネスに影響を与えてきたすべてのプロジェクト...また、使用されるスニペットはJavaスニペットですが、これは多くのオブジェクト指向言語に簡単に適用できます。


KISSYAGNIに従うだけで大丈夫です。
バーナード

2
この質問が表示される唯一の理由は、信じられないほど広くて制約のないPlayerおよびGameStateパラメーターのために、関数が非常に多くの状態にアクセスするためです。
ラースヴィクルンド

回答:


18

2つのインターフェイスが同じコントラクトを持っているからといって、同じインターフェイスであることを意味するわけではありません。

Liskov Substitution Principleの状態:

q(x)をT型のオブジェクトxについて証明可能なプロパティとする

または、言い換えれば、インターフェースまたはスーパータイプに当てはまるものは、そのすべてのサブタイプにも当てはまるはずです。

あなたの説明を適切に理解していれば、能力は効果ではなく、効果は能力ではありません。どちらか一方が契約を変更した場合、もう一方が契約を変更することはほとんどありません。それらを互いにバインドしようとする正当な理由はありません。


2

ウィキペディアから:「インターフェイスは、データを含まない抽象型を定義するためによく使用されますが、メソッドとして定義された動作を公開します」。私の意見では、インターフェースは振る舞いを記述するために使用されるため、異なる振る舞いがある場合は、異なるインターフェースを持つことは理にかなっています。あなたの質問を読んで、私が得た印象は、あなたは異なる行動について話しているので、異なるインターフェースが最良のアプローチかもしれないということです。

あなた自身が言ったもう一つのポイントは、それらの行動の1つが変わる場合です。次に、インターフェイスが1つしかない場合はどうなりますか?


1

カードゲームのルールで「効果」と「能力」を区別する場合は、それらが異なるインターフェイスであることを確認する必要があります。これにより、他の1つが必要な場所で誤って使用することを防ぎます。

ただし、それらが非常に類似している場合は、共通の祖先から派生させるのが理にかなっています。慎重に考えてみましょう:あなたは、「エフェクト」と「能力」は常になると信じる理由なければならないのは必ずしも同じインタフェースを持っているの?effectインターフェイスに要素を追加する場合、同じ要素をabilityインターフェイスに追加する必要がありますか?

その場合、そのような要素は、feature両方の派生元である共通のインターフェイスに配置できます。そうでない場合、それらを統合しようとするべきではありません-基本インターフェースと派生インターフェース間で物を移動するのに時間を浪費します。ただし、「繰り返してはいけない」こと以外に実際に共通の基本インターフェースを使用するつもりはないため、それほど大きな違いはないかもしれません。そして、あなたがその意図に固執するなら、私の推測では、最初に間違った選択をした場合、後で修正するためのリファクタリングは比較的簡単かもしれません。


0

あなたが見ているのは、基本的に型システムの限定された表現力のアーティファクトです。

理論的には、タイプシステムでこれら2つのインターフェイスの動作を正確に指定できる場合、動作が異なるため、シグネチャは異なります。実際には、型システムの表現力は、停止問題やライスの定理などの基本的な制限によって制限されるため、行動のすべての側面を表現できるわけではありません。

現在、異なるタイプのシステムでは表現力が異なりますが、常に表現できないものがあります。

たとえば、エラー動作が異なる2つのインターフェイスは、C#で同じシグネチャを持つことができますが、Javaではそうではありません(Javaの例外はシグネチャの一部であるため)。副作用が異なるだけの動作を持つ2つのインターフェイスは、Javaでは同じシグネチャを持つことがありますが、Haskellでは異なるシグネチャを持つことになります。

そのため、異なる動作に対して同じシグネチャを使用する可能性が常にあります。わずかなレベル以上でこれらの2つの動作を区別できることが重要だと思う場合は、より(または異なる)表現型システムに切り替える必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.