「下位」のアプリケーション層が「上位」のアプリケーション層に気付かないのはなぜ良い考えですか?


66

典型的な(適切に設計された)MVC Webアプリでは、データベースはモデルコードを認識せず、モデルコードはコントローラーコードを認識せず、コントローラーコードはビューコードを認識しません。(ハードウェアと同じくらい、あるいはそれ以上のレベルで開始することもでき、パターンも同じになると思います。)

別の方向に進むと、1つ下のレイヤーに移動できます。ビューはコントローラーを認識できますが、モデルは認識できません。コントローラーはモデルを認識できますが、データベースは認識できません。モデルはデータベースを認識できますが、OSは認識できません。(より深いものはおそらく無関係です。)

なぜこれが良いアイデアなのか直感的に把握できますが、明確に説明することはできません。なぜこの単方向スタイルのレイヤー化が良いアイデアなのでしょうか?


10
たぶん、データがデータベースからビューに「アップ」するからでしょう。データベースで「開始」し、ビューに「到着」します。レイヤー認識は、データが「移動する」のとは逆の方向に進みます。「引用」を使用するのが好きです。
ジェイソンスウェット

1
最後の文でタグ付けしました:一方向。リンクリストが二重リンクリストよりもはるかに一般的なのはなぜですか?単一リンクリストを使用すると、関係の維持が非常に簡単になります。再帰呼び出しが非常に少なくなり、一般的なグラフの特性が全体として推論しやすくなるため、この方法で依存関係グラフを作成します。合理的な構造は本質的に保守性が高く、マクロレベル(実装)でグラフに影響を与えるのと同じことは、マクロレベル(アーキテクチャ)でも同様です。
ジミー・ホッファ

2
ViewがControllerを認識することは、ほとんどの場合、良い習慣だとは実際には思いません。コントローラは、コントローラの意識ビューを持つ、ビューのほとんど常に認識しているので、作成循環参照
エイミーブランケン

8
悪い類推の時間:同じ理由で、あなたが運転しているときに車で後ろからあなたを叩く人は、一般的な場合に事故の責任と説明責任がある人です。彼はあなたが何をしているのかを管理でき、彼があなたを避けられなかったなら、それは彼が安全規則を尊重しなかったことを意味します。その逆ではありません。そして、連鎖することで、彼の背後で何が起こっているのか心配する必要がなくなります。
ヘイレム

1
明らかに、ビューは強く型付けされたビューモデルを認識しています。
-DazManCat

回答:


121

レイヤー、モジュール、実際にはアーキテクチャ自体は、コンピュータープログラムを人間が理解しやすくする手段です。問題を解決するための数値的に最適な方法は、ほとんどの場合、非モジュラー、自己参照、または自己修正コードの複雑な混乱です-数百万年後のメモリ制約やDNAシーケンスが組み込まれた組み込みシステムのアセンブラコードを大幅に最適化するかどうか選択圧力の。そのようなシステムには、層も、情報フローの識別可能な方向も、実際にはまったく識別できる構造もありません。著者以外の誰にとっても、彼らは純粋な魔法で働いているようです。

ソフトウェアエンジニアリングでは、それを避けたいです。良いアーキテクチャは、システムを普通の人が理解できるようにするために、効率を犠牲にするという意図的な決定です。一度に1つのことを理解する方が、一緒に使用した場合にのみ意味をなす2つのことを理解するよりも簡単です。それがモジュールとレイヤーが良いアイデアである理由です。

ただし、必然的にモジュールは相互に関数を呼び出す必要があり、レイヤーは互いの上に作成する必要があります。そのため、実際には、一部のパーツが他のパーツを必要とするようにシステムを構築することが常に必要です。望ましい妥協点は、ある部分が別の部分を必要とするような方法でそれらを構築することですが、その部分は最初の部分を必要としません。そして、これはまさに単方向の階層化によって得られるものです。ビジネスルールを知らなくてもデータベーススキーマを理解し、ユーザーインターフェイスを知らなくてもビジネスルールを理解することができます。両方の方向に独立していると良いでしょう-誰かが何も知らなくて新しいUIをプログラムできるようにするビジネスルールについてですが、実際にはこれは事実上不可能です。「周期的な依存関係なし」や「依存関係は1レベルだけ下がらなければならない」などの経験則は、一度に1つのことが理解しやすいという基本的な考え方の実際に達成可能な制限を単に捕らえます。


1
「システムを普通の人が理解できるようにする」とはどういう意味ですか?多くの人がそうであるように、彼らは彼らがほとんどの人よりも賢く、これは彼らにとって問題ではないと思うので、新しいプログラマーがあなたの良い点を拒否することを奨励すると言います。「システムを人間が理解できるようにする」と言うだろう
トーマスボニーニ

12
これは、完全なデカップリングが理想的であると考えているが、なぜ機能しないのか理解できない人のために読む必要があります。
ロバートハーヴェイ

6
@Andreasには、常にMelがいます。
TRiG

6
「理解しやすい」だけでは不十分だと思います。また、コードの変更、拡張、および保守を簡単にすることも重要です。
マイクウェラー

1
@Peri:そのような法律は見、存在しないen.wikipedia.org/wiki/Law_of_Demeterを。あなたがそれに同意するかどうかは別の問題です。
マイクチェンバレン14

61

基本的な動機は次のとおりです。レイヤー全体をリッピングし、完全に異なる(書き換えられた)レイヤーに置き換えることができるようにしたいと考えています。

最も明らかな例は、最下層を切り取り、別の層に置き換えることです。これは、ハードウェアのシミュレーションに対して上位層を開発し、次に実際のハードウェアに置き換えるときに行うことです。

次の例は、中間層をリッピングして別の中間層に置き換える場合です。RS-232で実行されるプロトコルを使用するアプリケーションを検討してください。「他の何かが変更された」ため、ある日、プロトコルのエンコーディングを完全に変更する必要があります。(例:ロサンゼルスのダウンタウンからマリーナデルレイへの無線リンクを介して作業していたため、現在はロサンゼルスのダウンタウンからヨーロッパを周回するプローブへの無線リンクを介して作業しているため、ASCIIストリームのASCIIエンコーディングからリードソロモンエンコーディングへの切り替え、木星の衛星の1つであり、そのリンクにはより優れた前方誤り訂正が必要です。

これを機能させる唯一の方法は、各レイヤーが既知の定義済みインターフェイスを上のレイヤーにエクスポートし、既知の定義済みインターフェイスを下のレイヤーに期待する場合です。

さて、下位層が上位層について何も知らないということは、正確にはそうではありません。むしろ、下層が知っているのは、そのすぐ上の層がその定義されたインターフェースに従って正確に動作するということです。定義により、定義されたインターフェイスにないものはすべて、予告なしに変更される可能性があるため、これ以上何も知ることができません。

RS-232レイヤーは、ASCII、Reed-Solomon、Unicode(アラビア語コードページ、日本語コードページ、Rigellianベータコードページ)、または何を実行しているかを知りません。バイトのシーケンスを取得し、それらのバイトをポートに書き込むことを知っているだけです。来週、彼は完全に異なるものから完全に異なるバイトのシーケンスを取得するかもしれません。彼は気にしません。彼はバイトを移動するだけです。

階層化設計の最初の(そして最良の)説明は、ダイクストラの古典的な論文「Structure of the THE Multiprogramming System」です。このビジネスで読む必要があります。


これは役に立ちます。リンクに感謝します。最良の回答として2つの回答を選択できるといいのですが。私は基本的に頭の中でコインをひっくり返し、もう片方を選びましたが、それでもあなたの投票を支持しました。
ジェイソンスウェット

優秀な例では+1。JRS
ViSu

@JasonSwett:もしコインをめくっていたら、その答えが示されるまでコインをめくっていたでしょう!^^ジョンに+1。
オリビエデュラック

ビジネスルールレイヤーをリッピングして別のレイヤーと交換できるようにすることはめったにないので、これには多少同意しません。ビジネスルールの変更は、UIやデータアクセステクノロジーよりもはるかに遅くなります。
アンディ

ディンディンディン!あなたが探していた言葉は「デカップリング」だと思います。それが優れたAPIの目的です。普遍的に使用できるようにモジュールのパブリックインターフェイスを定義します。
エヴァンプライス

8

より高いレベルが変化する可能性があるため。

その場合、要件の変更、新しいユーザー、さまざまな技術、モジュラー(つまり、単方向のレイヤード)アプリケーションが必要なメンテナンスが少なく、新しいニーズに合わせてより簡単に適応させる必要があります。


4

主な理由は、物事をより緊密に結びつけることだと思います。結合がきつくなるほど、後で問題が発生する可能性が高くなります。この記事の詳細を参照してください:カップリング

抜粋は次のとおりです。

欠点

密結合システムは、次のような開発上の特徴を示す傾向がありますが、これは多くの場合、欠点と見なされます。モジュールの依存関係が増加するため、モジュールのアセンブリにはより多くの労力や時間が必要になる場合があります。特定のモジュールは、依存モジュールを含める必要があるため、再利用やテストが困難になる場合があります。

これは、パフォーマンス上の理由から、より優れた結合システムを使用する理由と言われています。私が言及した記事にも、これに関するいくつかの情報があります。


4

IMO、それは非常に簡単です。使用されているコンテキストを参照し続けるものを再利用することはできません。


4

レイヤーは双方向の依存関係を持つべきではありません

階層化アーキテクチャの利点は、層を独立して使用できることです。

  • 下位層を変更せずに、最初の層に加えて別のプレゼンテーション層を構築できる必要があります(たとえば、既存のWebインターフェイスに加えてAPI層を構築します)
  • 最上位レイヤーを変更せずに下位レイヤーをリファクタリングするか、最終的に置き換えることができるはずです

これらの条件は基本的に対称です。一般的に、依存方向が1つだけの方が良いのはなぜかを説明しますが、whichはそうありません。

依存関係の方向はコマンドの方向に従う必要があります

トップダウンの依存構造を好む理由は、トップオブジェクトボトムオブジェクトを作成して使用するためです。依存関係とは、基本的に「AがB なしでは機能しない場合、A Bに依存する」という関係です。したがって、AのオブジェクトがBのオブジェクトを使用する場合、それが依存関係のあり方です。

これはいくぶんarbitrary意的です。MVVMなどの他のパタ​​ーンでは、制御は最下層から簡単に流れます。たとえば、表示キャプションが変数にバインドされ、それに伴って変化するラベルを設定できます。ただし、メインオブジェクトは常にユーザーが操作するものであり、これらのオブジェクトが作業の大部分を行うため、通常はトップダウンの依存関係を保持することをお勧めします。

上から下にメソッド呼び出しを使用し、下から(通常)イベントを使用します。イベントにより、コントロールが逆方向に流れる場合でも依存関係をトップダウンできます。最上層のオブジェクトは、最下層のイベントにサブスクライブします。一番下のレイヤーは、プラグインとして機能する一番上のレイヤーについて何も知りません。

たとえば、単一の方向を維持する他の方法もあります。

  • 継続(ラムダまたは呼び出されるメソッドとイベントを非同期メソッドに渡す)
  • サブクラス化(Bの親クラスのAにサブクラスを作成し、それをプラグインのように最下層に挿入します)

3

マットフェンウィックとキリアンフォスがすでに説明したものに2セントを加えたいと思います。

ソフトウェアアーキテクチャの1つの原則は、より小さな自己完結型ブロック(ブラックボックス)を構成して複雑なプログラムを構築することです。これにより、依存関係が最小限に抑えられ、複雑さが軽減されます。したがって、この一方向の依存関係は、ソフトウェアを理解しやすくするために良いアイデアであり、複雑さを管理することはソフトウェア開発の最も重要な問題の1つです。

したがって、階層化アーキテクチャでは、下位層はブラックボックスであり、その上に上位層が構築される抽象化層を実装します。下位層(たとえば、層B)が上位層Aの詳細を表示できる場合、Bはブラックボックスではなくなります。その実装の詳細は、ユーザー自身の詳細に依存しますが、ブラックボックスの考え方はコンテンツ(その実装)はユーザーにとっては無関係です!


3

ただ楽しみのために。

チアリーダーのピラミッドを考えてください。下の行は、上の行をサポートしています。

その列のチアリーダーが見下ろしている場合、彼らは安定しており、彼女の上の人が落ちないようにバランスを保ちます。

彼女が上にいる全員がどのように行動しているかを見上げると、彼女はバランスを失い、スタック全体が倒れます。

あまり技術的ではありませんが、助けになると思いました。


3

理解の容易さとある程度の交換可能なコンポーネントは確かに正当な理由ですが、同様に重要な理由(そしておそらくレイヤーが最初に発明された理由)はソフトウェア保守の観点からです。一番下の行は、依存関係が物事を壊す可能性を引き起こすことです。

たとえば、AがBに依存しているとします。Aに依存するものは何もないので、開発者はA以外のものを壊すことを心配することなく、Aを心のコンテンツに自由に変更できます。ただし、開発者がB Bで行われた場合、Aが壊れる可能性があります。これは、コンピューターの初期の頃に頻繁に発生する問題であり(構造化開発を考えてください)、開発者はプログラムの一部でバグを修正し、プログラムの他の部分でまったく無関係な部分でバグを発生させていました。すべては依存関係のためです。

この例を続けるために、AがBに依存し、BがAに依存すると仮定します。IOW、循環依存関係。現在、どこかで変更が行われると、他のモジュールが破損する可能性があります。Bの変更はAを壊す可能性がありますが、Aの変更はBを壊す可能性があります

したがって、元の質問では、小さなプロジェクトのために小さなチームにいる場合は、気まぐれにモジュールを自由に変更できるため、これはほとんどやり過ぎです。ただし、大規模なプロジェクトの場合、すべてのモジュールが他のモジュールに依存していると、変更が必要になるたびに他のモジュールが破損する可能性があります。大規模なプロジェクトでは、すべての影響を把握するのが難しいため、いくつかの影響を見逃す可能性があります。

多くの開発者がいる大規模なプロジェクトではさらに悪化します(たとえば、レイヤーA、レイヤーB、およびレイヤーCのみを操作する人)。変更が壊れたり、作業中の作業を強制的にやり直したりしないようにするために、各変更を他のレイヤーのメンバーと検討/議論する必要がある可能性が高くなります。あなたの変更が他の人に変更を強制する場合、あなたは彼らに変更を加えるべきであると納得させる必要があります。なぜなら、あなたはあなたのモジュールで物事を行うこの素晴らしい新しい方法があるからといって、彼らはより多くの仕事を引き受けたくないからです。IOW、官僚的な悪夢。

ただし、依存関係をAに限定し、Bに依存し、BをCに依存する場合、レイヤーCの人々だけが両チームへの変更を調整する必要があります。レイヤーBはレイヤーAチームと変更を調整するだけでよく、レイヤーAチームはコードがレイヤーBまたはCに影響しないため、好きなことを自由に行うことができます。理想的には、レイヤーCが非常に変化するようにレイヤーを設計します少し、レイヤーBはいくらか変化し、レイヤーAはほとんどの変化を行います。


+1私の雇用主には、私が取り組んでいる製品に適用される最後の段落の本質を説明する内部図が実際にあります。
RobV 14

1

下位層が上位層を認識してはならない最も基本的な理由は、より多くの種類の上位層があることです。たとえば、Linuxシステムには何千もの異なるプログラムがありますが、それらは同じCライブラリmalloc関数を呼び出します。そのため、依存関係はこれらのプログラムからそのライブラリへです。

「下層」は実際には中間層であることに注意してください。

一部のデバイスドライバーを介して外部と通信するアプリケーションを考えてください。オペレーティングシステムは中央にあります。

オペレーティングシステムは、アプリケーション内やデバイスドライバー内の詳細に依存しません。同じ種類のデバイスドライバーには多くの種類があり、同じデバイスドライバーフレームワークを共有します。カーネルハッカーは、特定のハードウェアまたはデバイスのために、特別なケース処理をフレームワークに入れる必要がある場合があります(最近見た例:LinuxのusbシリアルフレームワークのPL2303固有のコード)。それが起こるとき、彼らは通常それがどれだけひどいと削除されるべきかについてコメントを入れます。OSはドライバーの関数を呼び出しますが、呼び出しはドライバーを同じように見せるためのフックを通過しますが、ドライバーがOSを呼び出すと、特定の関数を名前で直接使用することがよくあります。

だから、いくつかの方法で、オペレーティングシステムは、実際のアプリケーションの観点から、下層である物事が接続してデータを適切な経路を進むように切り替えられる通信ハブの種類:アプリケーションの観点から。通信ハブの設計を支援し、あらゆるデバイスで使用できる柔軟なサービスをエクスポートし、デバイスまたはアプリケーション固有のハッキングをハブに移動しません。


特定のCPUピンに特定の電圧を設定することを心配する必要がない限り、私はうれしいです:)
CVn

1

懸念の分離と分割/征服のアプローチは、この質問の別の説明になります。懸念事項を分離することで移植性が得られ、一部のより複雑なアーキテクチャでは、プラットフォームに依存しないスケーリングとパフォーマンスの利点が得られます。

このコンテキストで、5層のアーキテクチャ(クライアント、プレゼンテーション、ビジネス、統合、リソース層)について考える場合、下位レベルのアーキテクチャは上位レベルのロジックとビジネスを認識してはならず、その逆も同様です。つまり、統合レベルとリソースレベルとしての下位レベルです。統合で提供されるデータベース統合インターフェースと実際のデータベースおよびWebサービス(サードパーティのデータプロバイダー)は、リソース層に属します。そのため、スケーラビリティなどの点で、MySQLデータベースをMangoDBのようなNoSQLドキュメントDBに変更するとします。

このアプローチでは、ビジネス層は、統合層がリソースによる接続/送信をどのように提供するかを気にしません。統合層によって提供されるデータアクセスオブジェクトのみを検索します。これはより多くのシナリオに拡張できますが、基本的には、懸念の分離がこの最大の理由になる可能性があります。


1

Kilian Fothの答えを拡張すると、この階層化の方向は、人間がシステムを探索する方向に対応します。

レイヤードシステムのバグを修正する新しい開発者であると想像してください。

バグは通常、顧客が必要とするものと彼が得るものとの不一致です。顧客がUIを介してシステムと通信し、UIを介して結果を取得すると(UIは文字通り「ユーザーインターフェイス」を意味します)、バグもUIの観点から報告されます。そのため、開発者としては、あまり多くの選択肢はありませんが、UIを調べて、何が起こったのかを把握する必要があります。

そのため、トップダウンレイヤー接続が必要です。さて、なぜ両方向の接続ができないのでしょうか?

さて、このバグが発生する可能性のあるシナリオは3つあります。

UIコード自体で発生する可能性があるため、そこでローカライズすることができます。これは簡単です。場所を見つけて修正するだけです。

UIからの呼び出しの結果として、システムの他の部分で発生する可能性があります。どちらかと言えば難しいですが、呼び出しのツリーをトレースし、エラーが発生した場所を見つけて修正します。

また、UIコードへの呼び出しの結果として発生する可能性があります。これは難しいので、呼び出しをキャッチし、そのソースを見つけて、エラーが発生した場所を特定する必要があります。開始点はコールツリーの単一のブランチの深い位置にあり、最初に正しいコールツリーを見つける必要があることを考慮すると、UIコードへの呼び出しがいくつかある可能性があるため、デバッグをカットアウトします。

最も困難なケースを可能な限り排除するために、循環依存関係は強く推奨されず、レイヤーはほとんどトップダウン方式で接続されます。他の方法で接続が必要な場合でも、通常は制限され、明確に定義されています。たとえば、コールバック(一種のリバース接続)でさえ、コールバックで呼び出されるコードは通常、最初にこのコールバックを提供し、リバース接続用の一種の「オプトイン」を実装し、理解への影響を制限しますシステム。

階層化はツールであり、主に既存のシステムをサポートする開発者を対象としています。まあ、レイヤー間の接続もそれを反映しています。


-1

ここで明示的に言及したい別の理由は、コードの再利用性です。交換されるRS232メディアの例はすでにあるので、さらに一歩進めましょう...

ドライバーを開発していると想像してください。そのあなたの仕事とあなたはかなりの束を書きます。物理メディアのように、プロトコルはおそらくある時点で繰り返し開始される可能性があります。

したがって、あなたがやり始めるのは、同じことを何度も繰り返し行うことの大ファンでない限り、これらのことのために再利用可能なレイヤーを書くことです。

Modbusデバイス用に5つのドライバーを作成する必要があるとします。それらの1つはModbus TCPを使用し、2つはRS485でModbusを使用し、残りはRS232を使用します。5つのドライバーを作成しているため、Modbusを5回再実装することはありません。また、下に3つの異なる物理層があるため、Modbusを3回再実装することはありません。

あなたがすることは、TCP Media Access、RS485 Media Access、そしておそらくRS232 Media Accessを書くことです。この時点で上にmodbusレイヤーがあることを知っているのは賢明ですか?おそらくない。実装する次のドライバーもイーサネットを使用しますが、HTTP-RESTを使用します。HTTP経由で通信するためにイーサネットメディアアクセスを再実装する必要がある場合は、残念です。

1つ上のレイヤーで、Modbusを一度だけ実装します。そのModbusレイヤーは、ドライバーを認識せず、1レイヤー上にあります。もちろん、これらのドライバーはmodbusと通信することになっていること、そしてイーサネットを使用していることを知っている必要があります。しかし、私が今説明した方法を実装したとしても、レイヤーを切り取って置き換えるだけではできません。もちろん、それがすべての最大のメリットであるということは、先に進んで、既存のイーサネットレイヤーを、その作成を最初に引き起こしたプロジェクトとはまったく関係のないものに再利用することです。

これは何か、おそらく毎日のように開発者と見なされ、時間を大幅に節約できます。あらゆる種類のプロトコルやその他のもののための無数のライブラリがあります。これらは、コマンドの指示に従う依存関係の指示のような原則のために存在します。これにより、ソフトウェアの再利用可能なレイヤーを構築できます。


再利用性は、すでに明示的に言及されてきた答えは半年以上前に投稿
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.