web-dev-qa-db-ja.com

レイヤードソフトウェアアーキテクチャで同じレイヤーのオブジェクト間に依存関係を持たせることは問題がありますか?

N層アーキテクチャと依存性注入を備えた中規模の大きなソフトウェアを考えると、ある層に属するオブジェクトは下位層のオブジェクトに依存することはできても、上位層のオブジェクトに依存することはできないと言えます。

しかし、同じレイヤーの他のオブジェクトに依存するオブジェクトについて何を考えればよいかわかりません。

例として、3つのレイヤーと画像にあるようないくつかのオブジェクトを持つアプリケーションを想定します。明らかに、トップダウンの依存関係(緑の矢印)は問題ありませんが、ボトムアップ(赤い矢印)は問題ありませんが、同じレイヤー(黄色の矢印)内の依存関係はどうですか?

enter image description here

循環依存関係を除いて、発生する可能性のある他の問題と、この場合にレイヤードアーキテクチャがどの程度違反されているかについて知りたいです。

12
bracco23

はい、1つのレイヤー内のオブジェクトcanは相互に直接依存関係があります。時には循環オブジェクトであっても、それが実際に、異なるレイヤーのオブジェクト間の許可された依存関係にコアの違いをもたらします。許可、または厳密な依存関係の方向。

ただし、そのような依存関係が恣意的に存在する必要があるという意味ではありません。それは実際には、レイヤーが何を表しているか、システムがどれほど大きいか、そしてパーツの責任がどうあるべきかに依存します。 「階層化アーキテクチャ」は曖昧な用語であり、実際にはさまざまな種類のシステムで何を意味するかには大きな違いがあることに注意してください。

たとえば、データベースレイヤー、ビジネスレイヤー、ユーザーインターフェイス(UI)レイヤーを備えた「水平方向に階層化されたシステム」があるとします。 UIレイヤーに少なくとも数十の異なるダイアログクラスが含まれているとしましょう。

どのダイアログクラスも別のダイアログクラスに直接依存しないデザインを選択できます。 「メインダイアログ」と「サブダイアログ」が存在し、「メイン」ダイアログから「サブ」ダイアログへの直接の依存関係しかないデザインを選択できます。または、既存のUIクラスが同じレイヤーの他のUIクラスを使用/再利用できるデザインを好むかもしれません。

これらはすべて可能な設計上の選択であり、構築するシステムのタイプに応じて多かれ少なかれ賢明かもしれませんが、どれもシステムの「階層化」を無効にしません。

10
Doc Brown

レイヤーに属するオブジェクトは、下位レイヤーのオブジェクトに依存する可能性があると言いやすいです

正直言って、あなたはそれに慣れるべきではないと思います。些細なシステム以外のものを扱う場合、すべてのレイヤーが他のレイヤーからの抽象化にのみ依存するようにすることを目指します。低くも高くも。

したがって、たとえば、Obj 1Obj 3に依存するべきではありません。これは、たとえばIObj 3に依存している必要があり、その抽象化のどの実装が実行時に動作するかを通知される必要があります。依存関係をマッピングするのが仕事なので、指示を行うことはどのレベルにも関係がないはずです。それはIoCコンテナ、たとえば純粋なDIを使用するmainによって呼び出されるカスタムコードかもしれません。または、プッシュでは、サービスロケータになることもできます。いずれにしても、マッピングが提供されるまで、依存関係はレイヤー間に存在しません。

しかし、同じレイヤーの他のオブジェクトに依存しているオブジェクトについて何を考えればよいかわかりません。

これがあなたがすべきが直接依存している唯一の時だと私は主張します。これはそのレイヤーの内部動作の一部であり、他のレイヤーに影響を与えることなく変更できます。したがって、それは有害な結合ではありません。

14
David Arno

これを実際に見てみましょう

enter image description here

Obj 3Obj 4が存在することを認識しました。だから何?なぜ気にするのですか?

[〜#〜] dip [〜#〜] は言う

「高レベルのモジュールは低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存するべきです。」

わかりましたが、すべてのオブジェクトが抽象化ではありませんか?

DIPも言う

「抽象化は詳細に依存するべきではありません。詳細は抽象化に依存するべきです。」

わかりましたが、オブジェクトが適切にカプセル化されていれば、詳細は隠されませんか?

すべてのオブジェクトにはキーワードインターフェースが必要だと盲目的に主張したい人もいます。私はその一人ではありません。私が盲目的にそれらを使用するつもりでなければあなたが後でそれらのような何かを必要とすることに対処する計画が必要であると主張するのが好きです。

すべてのリリースでコードが完全にリファクタリング可能であれば、必要に応じて後でインターフェースを抽出できます。再コンパイルしたくないコードを公開していて、インターフェースを介して話していることを望む場合は、計画が必要です。

Obj 3Obj 4が存在することを知っています。しかし、Obj 3は、Obj 4が具体的であるかどうかを知っていますか?

これがここにあるので、newをどこにも広めないのはとてもいいことです。 Obj 3Obj 4が具体的であるかどうかがわからない場合は、それが作成されていない可能性があります。後でひっくり返してObj 4を抽象クラスObj 3に変換すると、気にしません。

それができれば、Obj 4は完全に抽象的です。最初からそれらの間のインターフェイスを作成する唯一のことは、誰かがObj 4が現在具体的であることを放棄するコードを誤って追加しないという保証です。保護されたコンストラクターはそのリスクを軽減できますが、別の質問につながります。

Obj 3とObj 4は同じパッケージに含まれていますか?

オブジェクトはしばしば何らかの方法(パッケージ、名前空間など)でグループ化されます。グループ化すると、グループ全体ではなくグループ内の影響の可能性が高くなります。

機能ごとにグループ化するのが好きです。 Obj 3Obj 4が同じグループとレイヤーにある場合、1つを公開し、他の1つだけを変更する必要があるときにリファクタリングしたくない可能性はほとんどありません。つまり、これらのオブジェクトは、明確な必要性が生じる前に、それらのオブジェクトの間に抽象化を挿入することでメリットを得られる可能性が低くなります。

グループの境界を越えている場合は、どちらかの側のオブジェクトを個別に変化させることをお勧めします。

単純ですが、残念ながらJavaとC#の両方がこれを複雑にする不幸な選択をしています。

C#では、すべてのキーワードインターフェイスにI接頭辞を付けるのが伝統です。これにより、クライアントはキーワードインターフェイスと通信していることを知る必要があります。それはリファクタリング計画をいじくります。

Javaより良い命名パターンを使用するのは伝統です:FooImple implements Fooただし、Javaがキーワードインターフェイスをコンパイルするため、これはソースコードレベルでのみ役立ちますつまり、Fooを具体的なクライアントから抽象的なクライアントにリファクタリングするときに、コードの1文字を変更する必要がない場合でも、再コンパイルする必要があります。

これらの特定の言語のバグは、人々が本当にそれを必要とするまで、正式な抽象化を先延ばしにすることができないようにします。使用している言語は言っていませんが、これらの問題を抱えていない言語があることを理解しています。

どの言語を使用しているかは言わなかったので、どこでもキーワードインターフェースになると決める前に、言語と状況を注意深く分析することをお勧めします。

ここではYAGNIの原則が重要な役割を果たします。しかし、「足元で自分を撃たないようにしてください」もそうです。

4
candied_orange

上記の回答に加えて、別の視点から見た方が参考になると思います。

たとえば、 依存関係ルール の観点から。 DRは、Robert C. Martinが有名なClean Architectureとして提案した規則です。

それは言う

ソースコードの依存関係は、より高いレベルのポリシーに向かって、内側のみを指す必要があります。

上位レベルのポリシーにより、彼は抽象化の上位レベルを意味します。具体的なクラスやデータ構造とは対照的に、インスタンスのインターフェイスや抽象クラスなど、実装の詳細でリークするコンポーネント。

問題は、ルールがレイヤー間の依存関係に限定されないことです。それは、それらが属する場所やレイヤーに関係なく、コードの断片間の依存関係を指摘するだけです。

したがって、同じレイヤーの要素間に依存関係があることには本質的に問題はありません。ただし、依存関係は 安定した依存関係の原則 で伝達するように実装できます。

別の見方はSRPです。

デカップリングは、有害な依存関係を解消し、依存関係の逆転(IoC)などのいくつかのベストプラクティスを伝達する方法です。ただし、変更する理由を共有する要素は分離の理由を提供しません。同じ変更理由を持つ要素は同時に変更され(非常に可能性が高い)、それらも同時に展開されるためです。その場合はObj3およびObj4その後、もう一度、本質的に問題はありません。

0
Laiv