12

equals(a) C の 2 つのインスタンスが等しいかどうかを判断するのに十分な情報がない場合、または( hashCodeb) 呼び出しメソッドC の 2 つのインスタンスが等しいかどうかを判断できないはずです。

たとえば、私のプロジェクトでは、PlayingCardクラスがあります。PlayingCardaが表向きの場合、呼び出しメソッドはそのプロパティにアクセスできるように思われますが、裏向きの場合、それらのプロパティは不明のままにする必要があります。

class PlayingCard {
    private Rank rank;
    private Suit suit;
    private boolean isFaceDown;

    public PlayingCard(Rank rank, Suit suit, boolean isFaceDown) {
        this.rank = rank;
        this.suit = suit;
        this.isFaceDown = isFaceDown;
    }

    public Rank getRank() { return isFaceDown ? null : rank; }

    public Suit getSuit() { return isFaceDown ? null : suit; }

また、Java Collections Framework のために、2 枚のトランプ カードが同じランクとスーツを持っている場合、それらは等しくなければならないようです。

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(rank != other.rank) return false;
        if(suit != other.suit) return false;
        return true;
    }
 }

しかし、それはあまりにも多くの情報を明らかにします:

class Malicious {

    public Rank determineRankOfFaceDownCard(PlayingCard faceDownCard) {
        Set<PlayingCard> allCards = /* a set of all 52 PlayingCards face up */;
        for(PlayingCard c : allCards) {
            if(c.equals(faceDownCard)) {
                return c.getRank();
            }
        }
        return null;
    }
}

および getSuit` メソッドを使用してgetRankも機能しないようです。

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(getRank() != other.getRank()) return false;
        if(getSuit() != other.getSuit()) return false;
        return true;
    }
}

/* outside the PlayingCard class */

Set<PlayingCard> s = new HashSet<PlayingCard>();
s.add(new PlayingCard(Rank.ACE, Suit.SPADES, true));
s.contains(new PlayingCard(Rank.TWO, Rank.HEARTS, true)); // returns true

他の開発者はこの状況にどのように対処しましたか? これは、ある種の投げるのRuntimeExceptionが適切な状況ですか? ご意見やアイデアをお寄せいただきありがとうございます。

4

6 に答える 6

6

この条件を equals メソッドに追加できます。

if(this.isFaceDown || other.isFaceDown) return false;

裏向きのカードを完全に隠す唯一の方法だと思います。問題は、セットに追加するときに、追加するカードが裏向きの場合、重複する可能性があることです。

于 2012-06-19T15:25:38.123 に答える
3

何かが欠けているかどうかはわかりませんが、コンパレーターを保持しているクラスの外に、表示されていないときにカードを比較しないロジックがあるはずですか?

于 2012-06-19T15:29:44.027 に答える
3

ここでポリモーフィズムが最良の答えであるとは確信していません。カードが表向きか裏向きかによってカードが変わるわけではなく、それはカードの状態です。ロジックの場所の問題です。equals()カードが裏向きであることをアプリが認識している場合、なぜ同じかどうかをわざわざチェックするのでしょうか。表向きと裏向きを区別する必要はありません。equals().

ここで他の回答のいくつかの提案について詳しく説明すると、1 つが裏向き (ドロー デッキのように) または表向き (捨て札のように) であるさまざまなコレクションを識別する方法があるかもしれませんが、OP がしなかった多くの状況があります。スタックのアイデアがあまり役に立たない場所を指定します。標準的なソリティアでは、3 つの組み合わせのいずれかを持つことができます: すべて裏向き (プレイヤーが一番上のカードをめくろうとしていると仮定)、すべて表向き、または下の裏向きと 1 つ以上の表向きの組み合わせ。上にカード。ブラックジャックでは、(プレイ中のカードの) スタックのサイズは常に 1 であり、通常、プレイヤーはすべて表向きのカードを持ち、ディーラーは 1 枚の裏向きのカードと 1 枚以上の表向きのカードを持ちます。

メソッドに焦点を当てることは、.equals()時期尚早の最適化のようなものです。一歩下がって、ドメインについて考えてみてください。Cardと の属性を持つがSuiteありRankます。ADeckは単にCard(s) のコレクションであり、すべての複雑さは実際にはデッキを使用しているゲームに属します。たぶん、いくつかの属性を持つBlackjackを定義するでしょうHand。まず、ハンドのポイント値を決定するロジックを持つことができる のコレクションでありCard、それが「ソフト」か「バスト」かなどです。2 つのハンドを比較するときは、個々のカードを比較するのではなく、ハンドの最終的な値を比較します。これは、ディーラーの場合、裏向きのカードを持っている間でも有効です。

質問の悪意のあるシナリオは、誰のシステムがゲームを実行しているのかを尋ねなければならないという点で疑わしいようです。マルチクライアント システムの場合、それは妥当な懸念事項かもしれませんが、答えは、各クライアントに全員のカードの状態を与えないことです。この種のチートを防止するための典型的なアーキテクチャは、ニュートラルな VM/サーバーがゲームの状態を管理し、勝敗を評価することです。ゲームがマルチクライアントではない場合、ゲームを実行している CPU がゲーム データに完全にアクセスできる場合でも、影響を受けるプレイヤーは 1 人 (CPU の所有者) だけであり、悪意のある改ざんを防止しようとする長い歴史があります。失敗 - 過去 30 年間にゲームに使用されたコピー防止スキームについて読んでください - 悪意のあるシナリオは、複雑な設計ソリューションを試みるにはあまりにもまれ/妄想的であるように思えます.

とにかく、それが私が問題にアプローチする方法です。

于 2012-06-19T15:36:07.547 に答える
1

この質問には、いくつかの異なるレイヤーがあるようです...

しかし、あなたが煮詰めていると感じているのは、表向きか裏向きかによって、カードにはおそらく2つのクラスが必要だということです.

OO クラスを設計するときは、表向きか裏向きかでカードの扱いを変えるかどうかについて、論理的に考えることが有益です。

適切に設計されたクラス システムの規則は次のようになると思います: 2 枚の裏向きのカードには equals メソッドを含めるべきではありません。他のゲーム ロジックからそれらを比較するかどうかを決定できるからです。

于 2012-06-19T15:28:54.180 に答える
1

matches「不明なプロパティ」のゲームロジックを実装するために、別の等価メソッドを使用して、おそらくそれを呼び出しますがequals、コレクションなどの他のクラスが正常に機能するように、通常の方法でメソッドを実装します。このようにして、PlayingCard オブジェクトの Collection を持つことができ、プレーヤーがそれらのカードの価値を知っているかどうかに関係なく、たとえばデッキにスペードのエースが 2 つないことを保証できます。

たとえば、次のようになります。

abstract class PlayingCard {
    protected Rank rank;
    protected Suit suit;

    public PlayingCard(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public abstract Rank getRank();
    public abstract Suit getSuit();
    public abstract boolean isComparableWith(PlayingCard other);
    public abstract boolean matches(PlayingCard other);

    @Override public boolean equals(Object obj) {
        boolean isEqual = false;
        if (obj == null || !(obj instanceof PlayingCard)) {
            isEqual = false;
        } else if (obj == this) {
            isEqual = true;
        } else {
            PlayingCard other = (PlayingCard) obj;
            isEqual = (other.rank.equals(rank)) && (other.suit.equals(suit));
        }
        return isEqual;
    }
}

class FaceUpPlayingCard extends PlayingCard {
    public FaceUpPlayingCard(Rank rank, Suit suit) {
        super(rank, suit);
    }
    public boolean isComparableWith(PlayingCard other)  {
        return other instanceof FaceUpPlayingCard;
    }
    public boolean matches(PlayingCard other) {
        return isComparableWith(other) && equals(other);
    }
    public Rank getRank() { return rank; }
    public Suit getSuit() { return suit; }
}

class FaceDownPlayingCard extends PlayingCard {
    public FaceDownPlayingCard(Rank rank, Suit suit) {
        super(rank, suit);
    }
    public boolean isComparableWith(PlayingCard other)  {
        return false;
    }
    public boolean matches(PlayingCard other) {
        return false;
    }
    public Rank getRand() { return null; }
    public Suit getSuit() { return null; }
}

このように、コレクションがある場合、メソッドに基づいて並べ替えやその他の組み込みチェックを実行できます。このequalsメソッドは、カードの状態 (表向きまたは下向き) に関係なくプロパティをチェックします。ゲーム ロジックを実装する必要がある場合は、matchesメソッドを使用します。これは、両方のカードが表向きであるかどうかをチェックし、そうである場合は、ランクとスーツをチェックします。

于 2012-06-19T16:04:52.003 に答える
1

オブジェクトは互いに等しい必要があるだけです。クラス階層のさまざまなレベルでオブジェクトを表示できると考える場合

したがって、カードの表向きまたは裏向きをサポートしない は、カードの表向きまたは裏向きをサポートする の別Deckのサブクラスと等しい可能性があります。DeckequalDeck

ただし、PlayableDeck表向きまたは裏向きのカードをサポートする はequalDeckカードの位置をサポートしない ではありません。もしそうなら、2つは同等(交換可能)にPlayableDeckなり、カードの位置を知るという の能力を効果的に破壊するからです.

サブクラスとスーパークラスを念頭に置いて、平等のルールを適用することを忘れないでください。

  1. A = A
  2. A = B の場合、B は A でなければなりません。
  3. A = B かつ B = C の場合、A は = C でなければなりません。

Deck最初は、ルール 2 は、aと aPlayableDeckがどのような状況でも等しくならないことを暗示しているように見えますが、そうではありません。PlayableDecka をDeckthenと見なすことにした場合は、

if Deck.equals((Deck)PlayableDeck); then ((Deck)PlayableDeck).equals(Deck)

それでもうまくいきます。ただし、定数キャストがなければ、失敗する必要がある equals メソッドは PlayableDeck のものです。

PlayableDeck.equals(PlayableDeck) // might fail, depending on state
PlayableDeck.equals(Deck) // always fails

これがうまく機能する理由は、スーパークラスの実装を試みる前に基本クラスの実装がチェックされるという事実を保証するポリモーフィシムに結びついています。

于 2012-06-19T15:48:04.213 に答える