一般的な(適切に設計された)MVC Webアプリでは、データベースはモデルコードを認識せず、モデルコードはコントローラーコードを認識せず、コントローラーコードはビューコードを認識しません。 (私はあなたがハードウェアまで、あるいはおそらくさらに遠くまで始めることさえできると想像します、そしてパターンは同じかもしれません。)
別の方向に行くと、1つ下のレイヤーに移動できます。ビューはコントローラを認識できますが、モデルは認識できません。コントローラはモデルを認識できますが、データベースは認識できません。モデルはデータベースを認識できますが、OSは認識できません。 (より深いものはおそらく無関係です。)
なぜこれが良いアイデアなのか直感的に理解できますが、はっきりとは言えません。 なぜこの一方向のレイヤー化スタイルが良いアイデアなのですか?
レイヤー、モジュール、確かにアーキテクチャ自体は、コンピュータプログラムを人間が理解しやすくするの手段です。問題を解決するための数値的に最適な方法は、ほとんどの場合、モジュール化されていない自己参照コードまたは自己変更コードであり、何百万年もの間、メモリの制約やDNAシーケンスが損なわれている組み込みシステムのアセンブラーコードが大幅に最適化されているかどうかに関係します選択圧力の。このようなシステムには、層がなく、情報の流れに識別可能な方向がありません。実際、私たちが識別できる構造もありません。著者以外の誰にとっても、彼らは純粋な魔法で働いているようです。
ソフトウェアエンジニアリングでは、それを避けたいと考えています。優れたアーキテクチャは、システムを一般の人々が理解できるようにするために、ある程度の効率を犠牲にする意図的な決定です。一度に1つのことを理解することは、一緒に使用する場合にのみ意味のある2つのことを理解するよりも簡単です。モジュールとレイヤーが良い考えである理由です。
ただし、必然的にモジュールは相互に関数を呼び出す必要があり、レイヤーは相互に重ねて作成する必要があります。したがって、実際には、一部の部品が他の部品を必要とするようにシステムを構築することが常に必要です。推奨される妥協策は、あるパーツが別のパーツを必要とするような方法でそれらを構築することですが、そのパーツは最初のパーツを必要としません。そして、これがまさに単方向レイヤリングによってもたらされるものです。ビジネスルールを知らなくてもデータベーススキーマを理解し、ユーザーインターフェイスを知らなくてもビジネスルールを理解することが可能です。双方向に独立性があれば、ビジネスルールについてanythingをまったく知らなくても新しいUIをプログラミングできるようになればいいのですが、実際には、これは事実上不可能です。 「循環的な依存関係はありません」や「依存関係は1レベルしか下がらない」などの経験則は、一度に1つずつ理解するのは2つより理解しやすいという基本的な考え方の実際に達成可能な限界を単純に捉えます。
基本的な動機はこれです:レイヤー全体をリッピングして完全に異なる(書き換えられた)レイヤーに置き換えることができ、NOBODYは違いに気づく(できるようにする)必要があります。
最も明白な例は、最下層を取り除き、別の層を置き換えることです。これは、ハードウェアのシミュレーションに対して上位層を開発し、実際のハードウェアで置き換えるときに行うことです。
次の例は、中間層をリッピングして別の中間層を置き換える場合です。 RS-232で実行されるプロトコルを使用するアプリケーションについて考えてみます。ある日、「何か他のものが変更された」ため、プロトコルのエンコーディングを完全に変更する必要があります。 (例:ストレートASCIIエンコーディングからASCIIストリームのReed-Solomonエンコーディングへの切り替え。これは、LAのダウンタウンからマリナデルレイへの無線リンクで作業していたためです。 、そして今あなたはLAのダウンタウンから木星の衛星の1つであるエウロパを周回する探査機までの無線リンクを使って作業していて、そのリンクはより良い前方誤り訂正が必要です。)
これを機能させる唯一の方法は、各レイヤーが既知の定義済みインターフェースを上のレイヤーにエクスポートし、既知の定義済みインターフェースを下のレイヤーに期待する場合です。
さて、下層が上層について何も知らないのは、必ずしもそうではありません。むしろ、下位層が知っているのは、そのすぐ上の層がその定義されたインターフェースに従って正確に動作するということです。定義上、定義されたインターフェースにないものは通知なしに変更される可能性があるため、これ以上何も知ることができません。
RS-232レイヤーは、ASCII、リードソロモン、Unicode(アラビア語コードページ、日本語コードページ、リゲルリアンベータコードページ)、または何を実行しているかを認識していません。それはそれがバイトのシーケンスを取得していて、それらのバイトをポートに書き込んでいることを知っているだけです。来週、彼は完全に異なるものから完全に異なるバイトシーケンスを取得する可能性があります。彼は気にしません。彼はバイトを移動するだけです。
レイヤードデザインの最初の(そして最良の)説明は、ダイクストラの古典的な論文 "Structure of the T.H.E. Multiprogramming System" です。このビジネスでは必読です。
より高いレベルのため 五月変更されます。
それが発生した場合、要件の変更、新しいユーザー、異なるテクノロジーのいずれであっても、モジュラー(つまり、単方向に階層化された)アプリケーションは、メンテナンスが少なくて済み、新しいニーズに簡単に適合できるはずです。
主な理由は、それが物事をより密接に結合させることだと思います。結合が密であるほど、後で問題が発生する可能性が高くなります。この記事の詳細を参照してください: カップリング
以下は抜粋です。
短所
密結合システムは、以下の開発特性を示す傾向があり、これはしばしば欠点と見なされます。1つのモジュールの変更は、通常、他のモジュールの変更の波及効果を強制します。モジュール間の依存性の増大により、モジュールの組み立てには、より多くの労力や時間が必要になる場合があります。依存するモジュールを含める必要があるため、特定のモジュールの再利用やテストが難しい場合があります。
ティガー結合システムを使用する理由は、パフォーマンス上の理由からです。私が言及した記事にも、これに関するいくつかの情報があります。
IMO、それはとても簡単です。使用されているコンテキストを参照し続けるものを再利用することはできません。
レイヤードアーキテクチャの利点は、レイヤーを個別に使用できることです。
これらの条件は基本的に対称です。それらは、一般に、依存関係の方向が1つだけの方が良いが、whichではない理由を説明しています。
トップダウンの依存関係構造を好む理由は、topオブジェクトがbottomオブジェクト。依存関係は基本的に、「AがBなしで機能しない場合が機能しない場合に、AはBに依存する」という関係です。したがって、AのオブジェクトがBのオブジェクトを使用する場合、依存関係はこのようになります。
これはある程度恣意的です。 MVVMなどの他のパターンでは、制御は最下層から簡単に流れます。たとえば、表示されるキャプションが変数にバインドされ、それに応じて変化するラベルを設定できます。ただし、メインオブジェクトは常にユーザーが操作するオブジェクトであり、これらのオブジェクトが作業の大部分を実行するため、通常はトップダウンの依存関係を保持することをお勧めします。
上から下へとメソッド呼び出しを使用しますが、下から上へ(通常)イベントを使用します。イベントを使用すると、コントロールが逆方向に流れる場合でも、依存関係をトップダウンにすることができます。最上層のオブジェクトは、最下層のイベントをサブスクライブします。最下層は、プラグインとして機能する最上層について何も知りません。
たとえば、単一方向を維持する他の方法もあります。
楽しみのためだけに。
チアリーダーのピラミッドを考えてみてください。下の行はそれらの上にある行をサポートしています。
その列のチアリーダーが下を向いている場合、彼らは安定していて、彼女の上にいる人が落ちないようにバランスが保たれます。
彼女が上にいる全員の様子を見ると、バランスが崩れ、スタック全体が落ちてしまいます。
あまり技術的ではありませんが、これは私が役立つと思われる類推でした。
マットフェンウィックとキリアンフォスがすでに説明した内容に2セントを加算したいと思います。
ソフトウェアアーキテクチャの1つの原則は、複雑なプログラムは、小さな自己完結型ブロック(ブラックボックス)を作成することによって構築する必要があることです。これにより、依存関係が最小限に抑えられ、複雑さが軽減されます。したがって、この単方向の依存関係は、ソフトウェアの理解を容易にするため、良い考えです。複雑さの管理は、ソフトウェア開発における最も重要な問題の1つです。
したがって、階層化アーキテクチャでは、下位層はブラックボックスであり、上位層が構築される上に抽象化層を実装します。下位層(たとえば、層B)が上位層Aの詳細を見ることができる場合、Bはもはやブラックボックスではありません。その実装の詳細は、ユーザー自身の詳細に依存しますが、ブラックボックスの考え方は、コンテンツ(その実装)はユーザーには関係ありません!
理解しやすさやある程度交換可能なコンポーネントは確かに良い理由ですが、同様に重要な理由(そしておそらく最初にレイヤーが発明された理由)は、ソフトウェアのメンテナンスの観点からです。要するに、依存関係は物事を壊す可能性を引き起こすということです。
たとえば、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はほとんどの変化を行います。
懸念の分離と分割/統治のアプローチは、この質問に対する別の説明となる可能性があります。関心の分離は、移植性の機能を提供し、一部のより複雑なアーキテクチャでは、プラットフォームに依存しないスケーリングとパフォーマンスの利点を提供します。
このコンテキストでは、5層アーキテクチャー(クライアント、プレゼンテーション、ビジネス、統合、およびリソース層)について考える場合、低レベルのアーキテクチャは高レベルのロジックとビジネスを認識してはならず、その逆も同様です。統合およびリソースレベルとして、より低いレベルを意味します。統合で提供されるデータベース統合インターフェースと、実際のデータベースおよびWebサービス(サードパーティのデータプロバイダー)は、リソース層に属します。そのため、スケーラビリティなどに関して、MySQLデータベースをMangoDBのようなNoSQLドキュメントDBに変更するとします。
このアプローチでは、ビジネス層は、統合層がリソースによる接続/送信をどのように提供するかを気にしません。統合層によって提供されるデータアクセスオブジェクトのみを検索します。これはより多くのシナリオに拡張できますが、基本的には、懸念の分離がこれの最大の理由である可能性があります。
下位層が上位層を認識してはならない最も基本的な理由は、より多くの種類の上位層があるためです。たとえば、Linuxシステムには何千もの異なるプログラムがありますが、それらは同じCライブラリmalloc
関数を呼び出します。したがって、依存関係はこれらのプログラムからそのライブラリーへのものです。
「下位層」は実際には中間層であることに注意してください。
一部のデバイスドライバーを介して外界と通信するアプリケーションについて考えてみます。オペレーティングシステムは中央です。
オペレーティングシステムは、アプリケーション内やデバイスドライバー内の詳細には依存しません。同じタイプのデバイスドライバーには多くの種類があり、同じデバイスドライバーフレームワークを共有します。カーネルハッカーは、特定のハードウェアやデバイスのために、特別なケースの処理をフレームワークに組み込む必要がある場合があります(最近の例:Linuxのusb-serialフレームワークのPL2303固有のコード)。それが起こるとき、彼らは通常それがどれほどひどくて、取り除かれるべきであるかについてのコメントを入れます。 OSがドライバーの関数を呼び出す場合でも、ドライバーは同じように見えるフックを経由しますが、ドライバーがOSを呼び出す場合、多くの場合、特定の関数を名前で直接使用します。
したがって、いくつかの点で、オペレーティングシステムは、アプリケーションの観点から見ると実際には下位層ですおよびアプリケーションの観点から:物事が接続し、データが切り替えられて移動する一種の通信ハブ適切な経路。通信ハブの設計は、何でも使用できる柔軟なサービスをエクスポートし、デバイスやアプリケーション固有のハックをハブに移動しないようにするのに役立ちます。
Kilian Fothの答えを拡張すると、この階層化の方向は、人間がシステムを探索する方向に対応します。
あなたが、階層化システムのバグを修正する任務を持つ新しい開発者であると想像してください。
バグは通常、顧客が必要とするものと彼が得るものとの間のミスマッチです。顧客がUIを介してシステムと通信し、UIを介して結果を取得すると(UIは文字通り「ユーザーインターフェイス」を意味します)、バグもUIに関して報告されます。したがって、開発者としては、UIを調べて何が起こったのかを理解する以外に選択肢はありません。
そのため、トップダウンレイヤー接続が必要になります。さて、なぜ双方向の接続がないのですか?
まあ、そのバグが発生する可能性のあるシナリオは3つあります。
UIコード自体で発生する可能性があるため、そこでローカライズされます。これは簡単で、場所を見つけて修正するだけです。
UIからの呼び出しの結果として、システムの他の部分で発生する可能性があります。これは中程度に困難です。呼び出しのツリーをたどって、エラーが発生した場所を見つけて修正します。
そして、UIコードへの呼び出しの結果として発生する可能性があります。難しいのは、呼び出しをキャッチし、そのソースを見つけて、エラーが発生した場所を特定することです。開始するポイントが呼び出しツリーの単一の分岐の深いところにあり、最初に正しい呼び出しツリーを見つける必要があることを考えると、UIコードへの呼び出しが複数ある可能性があり、デバッグが省略されています。
最も困難なケースをできる限り排除するために、循環依存はお勧めできません。レイヤーは主にトップダウン方式で接続します。逆の接続が必要な場合でも、通常は制限され、明確に定義されます。たとえば、一種のリバース接続であるコールバックでも、コールバックで呼び出されているコードは通常、最初にこのコールバックを提供し、リバース接続に一種の「オプトイン」を実装し、理解への影響を制限しますシステム。
レイヤリングはツールであり、主に既存のシステムをサポートする開発者を対象としています。まあ、レイヤー間のつながりもそれを反映しています。