web-dev-qa-db-ja.com

観測可能なパターンはこのシナリオに適したツールですか?

卓上ゲームのPlayerTowerを構築したいBoardがあるとします。

これに対する私の最初のアプローチは次のようなものでした:

function onConfirm() {
  player.consumeResources();
  board.addTower();
}

このイベントはプレーヤーとボードのインターフェースを知っているため、ある程度のカップリングがあります。

一部の初期化手順でbuildTowerおよびBoardがサブスクライブされているオブザーバブルPlayerでオブザーバーパターンが使用された場合:

class Player() {
  onTowerBuilt() {
    this.decreaseResources();
  }
}

class Board() {
  onTowerBuilt() {
    this.addTower();
  }
}

function onConfirm() {
  buildTower.notify();
}

PlayerBoardはそれぞれ通知を受け取るため、プレーヤーのリソースを割引するためのロジックはプレーヤー内にあり、タワーをボードに配置するためのロジックはボード内にあります。私の見通しからカップリングを減らし、結束力を高めます。

したがって、この後、いくつかの質問:

  1. これはオブザーバーパターンの有効な使用ですか?

  2. このイベントの前に、プレイヤーがタワーを構築するのに十分なリソースを持っているかどうかを確認するためのチェックが必要だった場合、それを行うための良い方法は何でしょうか?

2
Mateo de Mayo

オブザーバーパターンは、インターフェースイベントをゲームロジックから分離するのに最適ですが、それとは別に、イベントを完全に処理するのに十分な情報を持つ単一のリスナーが必要な場合もあります。ゲームロジック内でイベントが発生するのを避けたいのですが、実行パスがわかりにくくなる傾向があります。

Playerがそれを処理する必要があるかどうかは不明ですが、プレーヤーのリソースを確認する必要がある場合は、次のような結果になります。

function onConfirm() {
  requestTower.notify();
}
class Player() {
  onTowerRequest() {
    if this.hasResources(){
        this.decreaseResources();
        this.board.addTower();
    }
  }
}

これには設計上の欠陥があります。これは、PlayerBoardを知っていることを意味します。考えてみると、Playerがプレーヤー関連情報のゲームコンテナーとして使用されている場合、このイベント処理は実際のPlayerオブジェクトに属する理由はほとんどありません。

そのため、通常はI/O処理をより高いレベルのクラスにグループ化し、たとえばGameと呼びます。このクラスがPlayerBoardの両方を持ち、それに応じて処理をディスパッチすることが論理的になります。

function onConfirm() {
  requestTower.notify();
}
class Game() { 
  onTowerRequest(){ 
      // May be necessary to solve who is requesting player here
      if player.hasResources(){
          player.decreaseResources();
          this.board.addTower();
      }
  }
}
2
Arthur Havlicek

これは、理論的にはさまざまな種類のオブジェクトをやや分離する、オブザーバーパターンの適切な使用法のようです。ただし、シナリオが十分かどうかはわかりません。

必要なリソースが異なるsmallTowerbigTowerがあるとします。シナリオに応じたオーケストレーションでは、構築するタワーの種類を知り、タワーオブジェクト(必要なリソースを知っている)を作成し、プレーヤーに十分なリソースがあるかどうかを確認して予約し、タワー通知をサブスクライブする必要があります。タワーを構築し、予約されたリソースを消費するためにタワーが構築されたことを要素に通知します。

ご覧のとおり、実際にはこのシナリオでは、通知が発生する前に、タワー、プレーヤー、ボードの相互依存関係があります。特にタワーがプレイヤーのものであることを考慮してください。さらに、ビルドできることを確認する前に、新しいオブジェクトをサブスクライブする必要があるのは奇妙です。

したがって、 ビルダーパターン の使用を検討することをお勧めします(注意:GoFビルダーであり、Blochのビルダーパターンではありません)。そのオブジェクトの責任は、建物が実行可能であることを確認してから、ボード、タワー、プレイヤー間の相互関係を調整することです。

このビルダーは、3つのオブジェクトのインターフェースを知っている必要があり、それらに依存します。しかし、他のオブジェクトは可能な限り疎結合のままです。

タワーに発生する可能性のあるすべてのイベントについて、オブザーバーを予測することもできます(タワーが終了し、タワーが破損し、タワーが破壊されます)。

2
Christophe

これは、オブザーバーパターンの使用に非常に適した候補です。これは、ケースでのカップリングが減少するためです。私の意見ではを使用すると、コンセプト全体がよりエレガントになります。

あなたの問題は非常に有効であり、もちろん、多くの異なるシナリオや多種多様なドメインで発生する可能性があります。ドメインイベントは、実際に最後に発生する場合と発生しない場合があります。 almost-occurringイベントの問題に対する最も典型的でエレガントな(ここでも私の意見では)ソリューションは、実際には1つのイベントではないことに気付いた後ですが、 2

2別個のイベントがsameハンドラーで順次発生する必要がありますandイベントと一緒にすべてのリスナーに渡される構造なので、潜在的な問題をハンドラーに通知できます。最初のイベントは検証ロジックを示し、2番目のイベントはactualドメインイベントの発生です。

間違いの可能性があるとは申し訳ありませんが、あなたの言語が何であるかは完全にはわかりません。以下が主なアイデアを適切に説明できることを願っています:

class Player() {
    onBuildTowerRequested(eventFeedback) {
      if (!this.hasResources)
      {
          eventFeedback.Cancel();
      }
    }

    onBuildTowerConfirmed() {
        this.decreaseResources();
    }
}

class Board() {
    onBuildTowerRequested(eventFeedback) {
      //Anything to validate?
    }    

    onBuildTowerConfirmed() {
      this.addTower();
    }
}

function onBuildTowerRequested(eventFeedback) {
    if (eventFeedback.cancel != true)
    {
        //Fire the confirmation event.
        //buildTower.notify() should NOT be simply called here because
        //listeners have additional logic to perform upon confirmation.
        //It is imperative that the onConfirmed event is fired as well.
    }
    //If the eventFeedback.cancel is true, do NOT fire the onConfirmed
    //event. This way, validation logic is decoupled from domain event
    //logic and you acquire wider flexibility in the range of scenarios
    //you can implement.
}

function onBuildTowerConfirmed() {
    //This will only be fired if no participating listener has cancelled the validation.
    buildTower.notify();
}

一般的な慣例では、イベントはonValidatingonValidatedのペアとしてペアになっていますが、ドメインと実装されたロジックに最適な名前を​​付けることができます。これら2つは順番に発生し、次々に発生します(onValidatingおよびthen onValidated、明らかに)。すべてのリスナーは、渡されたフィードバック構造(イベント引数)を通じてonValidatingイベントをキャンセルする機会があります。 onValidatingイベントの後、(リソース不足またはその他の理由で)誰もキャンセルを要求しなかった場合、onValidatedイベントが発生し、それに続くすべての変更は不可避であると宣言されていますが、発生する可能性も確認されています!一方、onValidatingイベントのフィードバック構造のcancel変数がtrueに設定されている場合、onValidatedイベントは発生/発生しません。

上記の例では、たとえばeventFeedback構造体がbool cancelに直接アクセスするのではなく、methodを使用してキャンセルを通知すると想定していることに注意してください。これは、たとえば、5つのリスナーの2番目がキャンセルをtrueに変更し、5番目のリスナーが(誤って)もう一度falseに変更するなどの問題を回避するためです。イベントの「リスニング」の順序が重要になります。これを使用する場合は、必ずメソッドを使用し、キャンセル変数を読み取り専用にしてください。リスナーのいずれかがキャンセルした場合、キャンセルは元に戻せません。リスナーにキャンセルのチェックを最初に行わせることもできます。これにより、前のリスナーによって検証が既にキャンセルされている場合に、高価な検証を実行する必要がなくなります。

2
Vector Zita