OOPで循環参照が必要と思われるこの実世界のアクティビティをモデル化する適切な方法は何ですか?


24

私は循環参照に関するJavaプロジェクトの問題に取り組んできました。私は、問題のオブジェクトが相互に依存しており、お互いについて知る必要があると思われる現実の状況をモデル化しようとしています。

このプロジェクトは、ボードゲームをプレイする一般的なモデルです。基本クラスは非特定ですが、チェス、バックギャモン、その他のゲームの特定に対処するために拡張されています。11年前、これを6種類のゲームを備えたアプレットとしてコーディングしましたが、問題は循環参照でいっぱいだということです。当時、私は単一のソースファイルにすべての絡み合ったクラスを詰め込むことでそれを実装しましたが、私はそれがJavaでは悪い形であるという考えを得る。ここで、Androidアプリと同様のものを実装し、適切に処理したいと考えています。

クラスは次のとおりです。

  • RuleBook:ボードの初期レイアウト、最初に移動した人などのその他の初期ゲームの状態情報、利用可能な移動、提案された移動後のゲームの状態に何が起こるか、などの評価などについて質問できるオブジェクト現在または提案されている取締役会の位置。

  • ボード:ゲームボードの単純な表現で、移動を反映するように指示できます。

  • MoveList:Moveのリスト。これは、特定のポイントで利用可能な動きの選択、またはゲーム内で行われた動きのリストの2つの目的です。それは2つのほぼ同一のクラスに分割できますが、それは私が尋ねている質問とは関係がなく、さらに複雑になるかもしれません。

  • 移動:1回の移動。移動に関するすべてが原子のリストとして含まれています。ここからピースを拾い、そこに置き、そこからキャプチャしたピースを削除します。

  • 状態:進行中のゲームの完全な状態情報。ボードの位置だけでなく、MoveList、および現在誰が移動するかなどの他の状態情報。チェスでは、各プレイヤーのキングとルークが移動したかどうかを記録します。

循環参照はたくさんあります。たとえば、RuleBookは特定の時間にどの動きが利用可能かを判断するためにゲームの状態を知る必要がありますが、ゲームの状態は最初の開始レイアウトと1回の動きに伴う副作用についてRuleBookに問い合わせる必要がありますそれが行われます(たとえば、次に移動する人)。

新しいクラスのセットを階層的に整理してみました。RuleBookはすべてを知る必要があるため、最上位にあります。しかし、これにより、多くのメソッドをRuleBookクラスに移動する(移動するなど)必要があり、モノリシックになり、RuleBookがどうあるべきかを特に表しません。

これを整理する適切な方法は何ですか?RuleBookをBigClassThatDoesAlmostEverythingInTheGameに変換して、循環参照を回避し、実際のゲームを正確にモデル化する試みを放棄する必要がありますか?または、相互に依存するクラスに固執し、コンパイラーに何らかの方法でそれらをコンパイルさせ、実世界のモデルを保持する必要がありますか?または、私が欠落している明らかな有効な構造がありますか?

あなたが与えることができる助けをありがとう!


7
どのような場合には、RuleBook例えば取っState引数として、および有効な返さMoveListすなわち、「次何ができるのか、私たちが今いる場所ここにあるの?」
jonrsharpe

@jonrsharpeが言ったこと。実際のボードゲームをプレイするとき、ルールブックはプレイされている実際のゲームについても知りません。実際に動きを計算するために別のクラスを導入することもありますが、それはこのRuleBookクラスがどれだけ大きいかによって異なります。
セバスチャンヴァンデンブローク

4
ゴッドオブジェクト(BigClassThatDoesAlmostEverythingInTheGame)を避けることは、循環参照を避けることよりもはるかに重要です。
user281377

2
しかし、@ user281377は必ずしも相互に排他的な目的ではありません!
jonrsharpe

1
モデリングの試みを見せてもらえますか?たとえば図?
ユーザー

回答:


47

私は循環参照に関するJavaプロジェクトの問題に取り組んできました。

Javaのガベージコレクターは、参照カウント手法に依存しません。循環参照は、Javaで問題を引き起こすことはありません。Javaで完全に自然な循環参照を削除するのに費やした時間は無駄です。

これをコーディングしました[...]が、問題は循環参照でいっぱいだということです。当時は、すべての絡み合ったクラスを1つのソースファイル詰め込んで実装しました [...]

必要はありません。すべてのソースファイルを一度にコンパイルする場合(例javac *.java:)、コンパイラはすべての前方参照を問題なく解決します。

または、相互に依存するクラスに固執し、コンパイラーにそれらをコンパイルするように勧める必要があります[...]

はい。アプリケーションクラスは相互依存することが期待されます。同じパッケージに属するすべてのJavaソースファイルを一度にコンパイルするのは巧妙なハックではなく、まさにJavaが機能するはずの方法です。


24
「循環参照は、Javaではいかなる種類の問題も引き起こしません。」コンパイルに関しては、これは事実です。ただし、循環参照は不適切な設計と見なされます。
チョップ

22
循環参照は多くの状況で完全に自然であるため、Javaおよびその他の現代言語では、単純な参照カウンターの代わりに洗練されたガベージコレクターが使用されます。
user281377

3
Javaは循環参照を解決できるのは素晴らしいことであり、多くの状況で自然なものであることは間違いありません。しかし、OPは特定の状況を提示し、それは考慮されるべきです。もつれたスパゲッティコードは、おそらくこの問題を処理する最良の方法ではありません。
マシュー

3
無関係なプログラミング言語について、根拠のないFUDを広めないでください。Pythonは、古くからリファレンスサイクルのGCをサポートしています(docs、SO:hereおよびhere)。
クリスチャンアイヒンガー

2
私見この回答は平凡なものです。なぜなら、OPの例にとって有用な循環参照についての言葉は1つもないからです。
ドックブラウン

22

付与された循環依存関係は、設計の観点からは疑わしい習慣ですが、禁止されているわけではありません。純粋に技術的な観点からは、必ずしも問題ではありません。ほとんどのシナリオでは、それらは状況によっては避けられず、まれに、役に立つものと見なされることさえあります。

実際、Javaコンパイラーが循環依存関係を拒否するシナリオはほとんどありません。(注:まだまだあるかもしれませんが、今は次のことしか考えられません)

  1. 継承:クラスAはクラスBを拡張できませんが、クラスBはクラスAを拡張します。これは論理的観点からはまったく意味がないため、これを持たないことは完全に合理的です。

  2. メソッドローカルクラス間:メソッド内で宣言されたクラスは、相互に循環参照することはできません。これはおそらくJavaコンパイラの制限に過ぎません。おそらく、そのようなことを行う能力は、それをサポートするためにコンパイラに入らなければならない追加の複雑さを正当化するほど有用ではないからです。(ほとんどのJavaプログラマーは、メソッド内でクラスを宣言できること、複数のクラスを宣言することはもちろん、これらのクラスが相互に循環参照できることを認識していません。)

したがって、循環依存を最小化するための探求は、技術的な正確さを探求するのではなく、設計の純度を探求することであることに気づき、それを回避することが重要です。

私が知る限り、循環依存を排除​​する還元主義的なアプローチはありません。つまり、循環参照を使用してシステムを取得し、それらを次々に適用し、終了するための単純な所定の「簡単な」手順のみで構成されるレシピはありません循環参照のないシステムを使用してください。頭を働かせて、デザインの性質に依存するリファクタリング手順を実行する必要があります。

あなたが手元にある特定の状況では、あなたが必要とするのは、おそらく他のすべてのエンティティを知っている「Game」または「GameLogic」と呼ばれる新しいエンティティであるようです(他のエンティティはそれを知らず、 )他のエンティティがお互いを知る必要がないように。

たとえば、RuleBookエンティティはGameStateエンティティについて何でも知る必要があるのは理不尽に思えます。なぜなら、ルールブックはプレイするために私たちが相談するものであり、プレイに積極的に関与するものではないからです。そのため、ルールブックとゲームの状態の両方を調べて利用可能な動きを決定する必要があるのはこの新しい「ゲーム」エンティティであり、これにより循環依存関係が排除されます。

今、私はあなたの問題がこのアプローチでどうなるかを推測できると思います:ゲームに依存しない方法で「ゲーム」エンティティをコーディングすることは非常に難しいので、おそらく1つだけでなく2つになるでしょうゲームの種類ごとにカスタムメイドの実装が必要なエンティティ:「RuleBook」および「Game」エンティティ。これは、そもそも「RuleBook」エンティティを持つという目的に反します。これについて言えることは、多分、たぶん、多くの異なる種類のゲームをプレイできるシステムを作成するというあなたの最初の願望は、高貴であったかもしれませんが、おそらく思いもよらないことです。私があなたの靴にいたなら、すべての異なるゲームの状態を表示するための共通のメカニズムと、これらすべてのゲームのユーザー入力を受け取るための共通のメカニズムを使用することに焦点を当てていたでしょう。


1
マイクありがとう。Gameエンティティの欠点については正しいです。古いアプレットコードを使用して、新しいRuleBookサブクラスと適切なグラフィックデザイン以外のほとんどの新しいゲームを作成することができました。
ダミアンウォーカー

10

ゲーム理論は、ゲームを以前の動きのリスト(誰がプレイしたかを含む値タイプ)および関数ValidMoves(previousMoves)として扱います

ゲームのUI以外の部分でこのパターンを試し、ボードのセットアップなどを移動として扱います。

UIは、ロジックへの一方向参照を備えた標準的なオブジェクト指向のものになります。


要約コメントへの更新

チェスを考えてください。チェスのゲームは、通常、動きのリストとして記録されます。http://en.wikipedia.org/wiki/Portable_Game_Notation

動きのリストは、ボードの絵よりもはるかに優れたゲームの完全な状態を定義します。

たとえば、Board、Piece、MoveなどのオブジェクトやPiece.GetValidMoves()などのメソッドの作成を開始するとします。

最初に、ボードを参照するピースを持つ必要がありますが、キャスティングを検討します。まだ王またはルークを移動していない場合にのみ行うことができます。そのため、キングとルークにMovedAlreadyフラグが必要です。同様に、ポーンは最初の動きで2マス移動できます。

それから、キャスリングでキングの有効な動きはルークの存在と状態に依存するので、ボードにはピースがあり、それらのピースを参照する必要があります。循環参照の問題に取り組んでいます。

ただし、Moveを不変の構造体として定義し、ゲームの状態を以前の移動のリストとして定義すると、これらの問題はなくなります。キャスリングが有効かどうかを確認するために、城と王の移動の存在の移動リストを確認できます。ポーンがパッシブになる可能性があるかどうかを確認するために、他のポーンが前の移動でダブルムーブしたかどうかを確認できます。ルール->移動以外の参照は必要ありません

現在、チェスには静的なボードがあり、ピースは常に同じ方法でセットアップされています。しかし、代替セットアップを許可するバリアントがあるとしましょう。おそらくハンディキャップとしていくつかの部分を省略します。

「ボックスから正方形Xへ」という移動としてセットアップの移動を追加し、その移動を理解するためにルールオブジェクトを調整すると、ゲームを移動のシーケンスとして表すことができます。

同様に、ゲーム内でボード自体が静止していない場合、チェスに正方形を追加したり、ボードから正方形を削除して移動できないようにすることができます。これらの変更は、ルールエンジンの全体的な構造を変更したり、同様のBoardSetupオブジェクトを参照したりすることなく、Moveとして表すこともできます。


これにより、ValidMovesの実装が複雑になり、ロジックが遅くなります。
-Taemyr

実際には、ボードのセットアップは可変であると想定しているため、何らかの方法で定義する必要があります。計算を支援するためにセットアップを他の構造体またはオブジェクトに移動する場合、必要に応じて結果をキャッシュできます。いくつかのゲームは、ボードの遊びとその変更があり、いくつかの有効な動きではなく、現在の位置よりも前の移動に依存することができます(例えばチェスにキャスリング。)
ユアン・

1
フラグやものを追加することは、単に移動履歴を持つことで回避できる複雑さです。ループにその高価ではない以上100のチェスの移動は、現在のボードのセットアップを得るために、あなたが動く間に結果をキャッシュできると言う
ユアン・

1
また、ルールを反映するようにオブジェクトモデルを変更することも避けます。つまり、チェスの場合、validMoves-> Piece + Boardを作成すると、ポーンや駒の昇格のためのキャスティング、通行不能、最初の移動に失敗し、オブジェクトに追加情報を追加するか、3番目のオブジェクトを参照する必要があります。また、誰が行くのかという考えと、発見された小切手などの概念も失います
ユアン

1
@Gabe boardLayoutはすべての関数ですpriorMoves(つまり、状態として維持した場合、それぞれ以外には何も提供されませんthisMove)。したがって、ユアンの提案は、本質的に「中間者をカットする」ことです。有効なのは、の代わりに、すべての事前の直接的な機能を動かしvalidMoves( boardLayout( priorMoves ) )ます。
OJFord

8

オブジェクト指向プログラミングで2つのクラス間の循環参照を削除する標準的な方法は、そのうちの1つで実装できるインターフェイスを導入することです。したがって、あなたの場合、どちらをRuleBook参照するStateかを参照する必要がありますInitialPositionProvider(これはによって実装されるインターフェースRuleBookです)。これにより、テストが簡単になりStateます。テスト用に別の(おそらくより単純な)初期位置を使用するを作成できるためです。


6

あなたの場合の循環参照と神オブジェクトは、ゲームの状態とルールモデルからゲームフローの制御を分離することで簡単に削除できると思います。そうすることで、おそらく多くの柔軟性を獲得し、不必要な複雑さを取り除くことができます。

ルールブックまたはゲームの状態にこの責任を与えるのではなく、ゲームの流れを制御し、実際の状態の変更を処理するコントローラー(必要に応じて「ゲームマスター」)が必要だと思います。

ゲーム状態オブジェクトは、それ自体を変更したり、ルールを認識する必要はありません。クラスは、アプリケーションの残りの部分に対して、簡単に処理(作成、検査、変更、永続化、ログ、コピー、キャッシュなど)し、効率的なゲーム状態オブジェクトのモデルを提供する必要があります。

ルールブックは、進行中のゲームについて知ったり、いじったりする必要はありません。どの動きが合法であるかを知ることができるのはゲーム状態のビューのみであり、ゲーム状態に動きが適用されたときに何が起こるかを尋ねられた場合、結果のゲーム状態で答える必要があります。また、初期レイアウトを求められたときにゲームの開始状態を提供することもできます。

コントローラーは、ゲームの状態とルールブック、およびゲームモデルの他のオブジェクトを認識している必要がありますが、詳細をいじる必要はありません。


4
まさに私の考え。OPは、同じクラスで大量のデータ手順を混同しています。これらをさらに分割することをお勧めします。これこのテーマに関する良い話です。ところで、「ゲームの状態を見る」と読んだとき、「機能に対する議論」と思います。可能であれば+100。
jpmc26

5

ここでの問題は、どのクラスがどのタスクを処理するのかを明確に説明していないことだと思います。各クラスが何をすべきかについての良い説明だと思うことを説明してから、アイデアを説明する汎用コードの例を示します。コードの結合性が低く、循環参照が実際にはないことがわかります。

各クラスの機能を説明するところから始めましょう。

GameStateクラスは唯一のゲームの現在の状態に関する情報が含まれている必要があります。ゲームの過去の状態や将来の動きについての情報を含めるべきではありません。チェスのどのマスにどのピースが入っているか、またはバックギャモンのどのポイントに何人のチェッカーがいるのかに関する情報のみが含まれている必要があります。GameStateチェスやバックギャモンで倍増キューブについてのキャスリングに関する情報と同様に、いくつかの追加情報が含まれている必要があります。

Moveクラスは少しトリッキーです。私は、ムーブをプレイしGameStateた結果を指定することで、プレイするムーブを指定できると言います。ですから、移動は単にとして実装できると想像できますGameState。ただし、(たとえば)goでは、ボード上の1つのポイントを指定することで、移動を指定する方がはるかに簡単だと想像できます。Moveこれらのケースのいずれかを処理できるように、クラスに十分な柔軟性が必要です。したがって、Move実際には、クラスはpre-move GameStateを取得して新しいpost-moveを返すメソッドとのインターフェースになりますGameState

現在、RuleBookクラスはルールに関するすべてを知る責任があります。これは3つのものに分類できます。イニシャルGameStateが何であるか、どの動きが合法であるかを知る必要があり、プレイヤーの一人が勝ったかどうかを知ることができる必要があります。

また、GameHistoryクラスを作成して、行われたすべての動きと発生したすべての動きを追跡することもできGameStatesます。新しいクラスが必要なのは、その前に来たGameStateすべてのGameStates を知ることは単一の責任ではないと判断したためです。

これで、これから説明するクラス/インターフェースを終了します。Boardクラスもあります。しかし、さまざまなゲームのボードは十分に異なっているため、ボードで一般的に何ができるかはわかりにくいと思います。次に、ジェネリックインターフェイスを提供し、ジェネリッククラスを実装します。

最初はGameStateです。このクラスは特定のゲームに完全に依存しているため、一般的なGamestateインターフェイスやクラスはありません。

次ですMove。前述したように、これは、移動前の状態を取得して移動後の状態を生成する単一のメソッドを持つインターフェイスで表すことができます。このインターフェイスのコードは次のとおりです。

package boardgame;

/**
 *
 * @param <T> The type of GameState
 */
public interface Move<T> {

    T makeResultingState(T preMoveState) throws IllegalArgumentException;

}

型パラメーターがあることに注意してください。これは、たとえば、 ChessMovepre-moveの詳細を知る必要があるためChessGameStateです。したがって、たとえば、クラス宣言はChessMoveなり

class ChessMove extends Move<ChessGameState>

すでにChessGameStateクラスを定義しているはずです。

次に、ジェネリックRuleBookクラスについて説明します。コードは次のとおりです。

package boardgame;

import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public interface RuleBook<T> {

    T makeInitialState();

    List<Move<T>> makeMoveList(T gameState);

    StateEvaluation evaluateState(T gameState);

    boolean isMoveLegal(Move<T> move, T currentState);

}

再び、GameStateクラスの型パラメーターがあります。RuleBookは初期状態が何であるかを知っているはずなので、初期状態を与えるメソッドを追加しました。RuleBookはどの動きが合法であるかを知っているはずなので、特定の状態で合法であるかどうかをテストし、特定の状態で合法的な動きのリストを提供するメソッドがあります。最後に、を評価するメソッドがありGameStateます。RuleBookいずれかのプレイヤーが既に勝ったかどうかを説明する責任があるだけで、ゲームの途中で誰がより良い位置にいるのではないことに注意してください。誰がより良い立場にあるかを決定することは複雑なことであり、それを独自のクラスに移すべきです。したがって、StateEvaluationクラスは実際には次のように指定された単なる列挙型です。

package boardgame;

/**
 *
 */
public enum StateEvaluation {

    UNFINISHED,
    PLAYER_ONE_WINS,
    PLAYER_TWO_WINS,
    DRAW,
    ILLEGAL_STATE
}

最後に、GameHistoryクラスについて説明しましょう。このクラスは、ゲームで到達したすべての位置とプレイされた動きを記憶する役割を果たします。できるはずの主なことはMove、再生されたままのレコードです。を元に戻す機能を追加することもできますMove。以下に実装があります。

package boardgame;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public class GameHistory<T> {

    private List<T> states;
    private List<Move<T>> moves;

    public GameHistory(T initialState) {
        states = new ArrayList<>();
        states.add(initialState);
        moves = new ArrayList<>();
    }

    void recordMove(Move<T> move) throws IllegalArgumentException {
        moves.add(move);
        states.add(move.makeResultingState(getMostRecentState()));
    }

    void resetToNthState(int n) {
        states = states.subList(0, n + 1);
        moves = moves.subList(0, n);
    }

    void undoLastMove() {
        resetToNthState(getNumberOfMoves() - 1);
    }

    T getMostRecentState() {
        return states.get(getNumberOfMoves());
    }

    T getStateAfterNthMove(int n) {
        return states.get(n + 1);
    }

    Move<T> getNthMove(int n) {
        return moves.get(n);
    }

    int getNumberOfMoves() {
        return moves.size();
    }

}

最後に、Gameすべてを結びつけるクラスを作成することを想像できます。このGameクラスは、現在の内容をGameState確認し、誰が誰を持っているのか、誰が持っているのか、どの動きをプレイできるのか、そして動きをプレイできるようにするメソッドを公開することになっています。私は以下の実装を持っています

package boardgame;

import java.util.List;

/**
 *
 * @author brian
 * @param <T> The type of GameState
 */
public class Game<T> {

    GameHistory<T> gameHistory;
    RuleBook<T> ruleBook;

    public Game(RuleBook<T> ruleBook) {
        this.ruleBook = ruleBook;
        final T initialState = ruleBook.makeInitialState();
        gameHistory = new GameHistory<>(initialState);
    }

    T getCurrentState() {
        return gameHistory.getMostRecentState();
    }

    List<Move<T>> getLegalMoves() {
        return ruleBook.makeMoveList(getCurrentState());
    }

    void doMove(Move<T> move) throws IllegalArgumentException {
        if (!ruleBook.isMoveLegal(move, getCurrentState())) {
            throw new IllegalArgumentException("Move is not legal in this position");
        }
        gameHistory.recordMove(move);
    }

    void undoMove() {
        gameHistory.undoLastMove();
    }

    StateEvaluation evaluateState() {
        return ruleBook.evaluateState(getCurrentState());
    }

}

このクラスではRuleBook、電流GameStateが何であるかを知る責任はありません。それがGameHistoryの仕事です。だから、現在の状態が何であるかをGame尋ね、法的動きが何であるかを言う必要がGameHistoryあるRuleBookとき、Gameまたは誰かが勝ったかどうかにこの情報を提供します。

とにかく、この答えのポイントは、各クラスが何に責任があるのか​​を合理的に決定し、各クラスを少数の責任に焦点を合わせ、各責任を一意のクラスに割り当ててから、クラス分離される傾向があり、すべてがコーディングしやすくなります。うまくいけば、それは私が与えたコード例から明らかです。


3

私の経験では、循環参照は一般的にあなたのデザインがよく考えられていないことを示しています。

あなたのデザインでは、RuleBookが州について「知る」必要がある理由がわかりません。確かに、何らかのメソッドのパラメーターとしてStateを受け取るかもしれませんが、なぜStateへの参照を知る(つまり、インスタンス変数として保持する)必要があるのでしょうか?それは私には意味がありません。RuleBookは、仕事をするために特定のゲームの状態を「知る」必要はありません。ゲームのルールは、ゲームの現在の状態に応じて変わりません。したがって、誤って設計したか、正しく設計したが間違って説明しています。


+1。物理的なボードゲームを購入すると、状態なしでルールを説明できるルールブックを入手できます。
unperson325680

1

循環依存は必ずしも技術的な問題ではありませんが、通常は単一の責任原則に違反するコードのにおいと見なされる必要があります。

循環依存関係は、Stateオブジェクトからあまりにも多くのことをしようとしているという事実に由来しています。

ステートフルオブジェクトは、そのローカル状態の管理に直接関連するメソッドのみを提供する必要があります。最も基本的なロジック以外のものが必要な場合は、おそらくより大きなパターンに分割する必要があります。一部の人々はこれについて異なる意見を持っていますが、一般的な経験則として、データのゲッターとセッター以外のことをしている場合、やり過ぎです。

この場合、あなたは持ったほうが良いと思いますStateFactory知っている可能性があります、Rulebook。おそらく、StateFactoryを使用して新しいゲームを作成する別のコントローラークラスがあるでしょう。State絶対に知ってはいけませんRulebook。ルールの実装に依存Rulebookすることを知っているかもしれませんState


0

ルールブックオブジェクトを特定のゲーム状態にバインドする必要がありますか、またはゲーム状態が与えられたときにその状態から利用可能な動きを報告するメソッドを含むルールブックオブジェクトを使用する方が理にかなっていますか(そして、それを報告したが、問題の州について何も覚えていない?使用可能な動きについて尋ねられるオブジェクトにゲーム状態のメモリを保持させることによって得られるものがない限り、参照を永続化する必要はありません。

場合によっては、ルールを評価するオブジェクトに状態を維持させると利点があります。このような状況が発生する可能性があると思われる場合は、「レフェリー」クラスを追加し、ルールブックに「createReferee」メソッドを提供することをお勧めします。ルールブックとは異なり、ルールブックは1つのゲームについて尋ねられても50の質問についても何も気にしません。レフェリーオブジェクトは1つのゲームを司会することを期待します。担当しているゲームに関連するすべての状態をカプセル化することは期待されていませんが、役に立つと思われるゲームに関する情報をキャッシュすることができます。ゲームが「元に戻す」機能をサポートしている場合、レフェリーに、以前のゲーム状態とともに保存できる「スナップショット」オブジェクトを作成する手段を含めると便利です。そのオブジェクトは、

コードのルール処理とゲーム状態処理の側面の間に何らかのカップリングが必要な場合、レフェリーオブジェクトを使用することで、メインのルールブックとゲーム状態クラスからそのようなカップリングを排除できます。また、ゲーム状態クラスが関連すると見なさないゲーム状態の側面を新しいルールに考慮させることも可能になります(たとえば、「場所Xに行った場合、オブジェクトXはYを実行できない」というルールが追加された場合「ゲーム状態クラスを変更することなく、どのオブジェクトがロケーションZに移動したかを追跡するために、レフリーを変更できます)。


-2

これに対処する適切な方法は、インターフェースを使用することです。2つのクラスにお互いを知ってもらう代わりに、各クラスにインターフェイスを実装させ、他のクラスのインターフェイスを参照させます。相互に参照する必要があるクラスAとクラスBがあるとします。クラスAにインターフェースAを実装させ、クラスBにインターフェースBを実装させます。クラスAからインターフェースBを、クラスBからインターフェースAを参照できます。他の両方のプロジェクトが参照しています。


2
これは単に繰り返しポイントが作られ、中で説明しているようだ前に答え、この1の前に数時間を掲示
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.