web-dev-qa-db-ja.com

親と子の間の密結合:常に回避すべきか?

実例のような例を使用して、2つの本質的に結合された要素を検討するとします。

  • Body
  • PhysicalIllness

注:次のコードは、構文の色分けのみを目的とした疑似Javaであり、純粋な構文はここでは特に問題になりません

Bodyは、staminafocusなどのいくつかの基本的なプロパティと、すべての病気を表すPhysicalIllnessの配列を保持するクラスです契約。

class Body {
  int focus
  int stamina

  PhysicalIllness[] physicalIllnesses
}

PhysicalIllnessは、具体的な病気によって拡張された抽象クラスです。ホストの体に依存するために彼らが行う/反応するすべて。彼らは体の中で生まれ、体の中で「生きて」おり、それらの存在は体の外では何も意味しません。

ご質問

このようなシナリオでは、-BodyのコンストラクターにPhysicalIllnessインスタンスが注入されず、(たとえば、Host_body)病気の生涯を通じて使用される参照、大丈夫ですか?その後、病気は人生の出来事に反応する可能性があります(sleepedhour_elapsed)単独で、それに応じてホスト本体に影響を与えます。

abstract class PhysicalIllness {
  Body hostBody

  PhysicalIllness(Body hostBody) {
    this.hostBody = hostBody
  }

  void onAcquired() {}
  void onHourElapsed() {}
  void onBodySleeped() {}
  void onGone() {}
} 
class Headache extends PhysicalIllness {
  void onAcquired() {
    this.hostBody.focus -= 10
  }
  void onHourElapsed() {
    this.hostBody.focus += 2
  }
  // ...
}

密結合は、実際にはここでは自然に思えます。ただし、BodyPhysicalIllnessインスタンスへの参照を保持し、PhysicalIllnessはその「親」であるホスト本体への参照も保持するため、循環/循環依存関係を生成します。

コードのメンテナンス/単体テスト、柔軟性などの点で、この方法で設計することの欠点を指摘していただけませんか?これについては他にも答えがあると思いますが、シナリオはそれぞれ異なるため、ここでも当てはまるかどうかはわかりません。

代替(循環依存なし)

1つの代替策は、PhysicalIllnessインスタンスに本体からすべてのイベントが通知されるようにすることで結合を削除することです(プロセスで引数として自分自身を渡します)。これには、PhysicalIllnessのすべてのメソッドにBodyパラメータが必要です。

abstract class Illness {
  void onAcquired(Body hostBody) {}
  void onHourElapsed(Body hostBody) {}
  // ...
}

class Headache extends Illness {
  void onAcquired(Body hostBody) {
    hostBody.focus -= 10
  }
  void onHourElapsed(Body hostBody) {
    hostBody.focus += 2
  }
  // ...
}
class Body {
  // ...

  void onHourElapsed() {
    for (PhysicalIllness illness in this.physicalIllnesses) {
      illness.onHourElapsed(this);
    }
  }

  // ...
}

これは不格好で実際には論理的ではないように思います。これは、体の病気が体の外に存在する可能性があることを意味します(ホストボディなしで体の病気を作ることができます)。したがって、すべてのメソッドに「明白な」Host_bodyパラメータ。

この投稿を1つの質問で要約する必要がある場合:すべての状況で、親/子コンポーネント間の密結合および/または循環依存を回避する必要がありますか?

2
Jeto

一般に、私は子オブジェクトがその親への参照を保持するのを避けようとします。振り返ってみると、私はこれをアンチパターンと呼ぶ誘惑に駆られるような方法で行われた十分に悪いデザインを見てきました。

確かに、ツリー構造をナビゲートするなど、それが理にかなっている場合がありますが、多くの場合、これはクラスの関心事以外の情報へのショートカットです。それは疑問を投げかけます、ボディはどの公開インターフェースを公開しますか?フォーカスを変更できるはずですか?

あなたの例を使用して、体に2つの病気がある場合を考えてみましょう。 Aはフォーカスを2ずつ減らし、Bはフォーカスを2倍にします。これらの2つの修飾子を適用する順序が重要になります。病気Bが存在するかどうかを確認するために、病気Aの検査を受けるつもりですか?魅力的な;しかし、それは非常に厄介なデザインにつながるでしょう。

代わりに親に病気の関数としてそのプロパティを計算させると、あなたの人生はより簡単になります。親は自身のイベントとそのすべての病気を知っており、病気は、親が計算を実行することを可能にする共通のインターフェース、例えばIllness.Precidenceなどを公開することができます。さらに、Parentクラスは独自のプロパティを自由に変更でき、パブリックインターフェイスを介してそれらを外部の変更に公開することを心配する必要があります。

このような関係の密結合にラベルを付けるかどうかはわかりません。あるクラスが別のクラスを参照しているため、クラスは密接に結合されています。しかし、あなたがしていることはさらに進んでいます

5
Ewan

双方向結合の回避はそれ自体が目的ではなく、目的を達成するための手段です。ここでは、最初にPhysicalIllnessオブジェクトを作成せずに、Bodyオブジェクトだけをインスタンス化することが不可能になる状況が発生します。これは問題になる場合とそうでない場合がありますが、これはケースに大きく依存します。

Bodyの作成に多大な労力が必要な場合、たとえばPhysicalIllnessオブジェクトの再利用とテストが困難になる場合にのみ問題が発生します。ただし、Bodyオブジェクトが単純で、(簡略化された)例に示されているメンバーよりも多くのメンバーが含まれていない場合、この設計はおそらく問題を引き起こしません。

配列が空になる可能性があるため、Bodyオブジェクトは必ずしもPhysicalIllnessオブジェクトを必要としないため、循環依存によって「hen-or-Egg」問題が発生することはありません。

必要に応じて、循環依存関係を解決するために、質問で提案されたものとは異なるアプローチがあります。インターフェイスIBodyを導入し、PhysicalIllnessのコンストラクターにIBodyを挿入させます。

PhysicalIllness(IBody hostBody) {
     this.hostBody = hostBody
}

IBodyは、PhysicalIllnessが必要とするメソッドを正確に提供できます。このように、PhysicalIllnessは「モックボディ」でインスタンス化される場合があります。これは、IBodyからも派生しますが、Bodyオブジェクトよりも作成が簡単です。

4
Doc Brown