web-dev-qa-db-ja.com

オブジェクト指向設計での疎結合

私はGRASPを学習しようとしていますが、これは低結合について説明されている( ここでは3ページ目 )とわかり、これを見つけたときはとても驚きました:

addTrackクラスのメソッドAlbumを考えます。2つの可能なメソッドは次のとおりです。

addTrack( Track t )

そして

addTrack( int no, String title, double duration )

カップリングを減らす方法はどれですか? 2番目のクラスは、Albumクラスを使用するクラスがTrackクラスを知っている必要がないためです。一般に、メソッドのパラメーターには、Java。*パッケージの基本型(int、char ...)とクラスを使用する必要があります。

私はこれに同意する傾向があります。さまざまな理由により、addTrack(Track t)addTrack(int no, String title, double duration)よりも優れていると思います。

  1. メソッドのパラメーターはできるだけ少なくする方が常に良いです(ボブおじさんのクリーンコードによると、なしまたは1つ、できれば1つ、場合によっては2つ、特別な場合は3つ。3つ以上はリファクタリングが必要です。これらはもちろん、ホリールールではなく推奨事項です) 。

  2. addTrackがインターフェイスのメソッドであり、Trackに詳細な情報(年やジャンルなど)が必要であるという要件が必要な場合は、インターフェイスを変更して、メソッドがサポートするようにする必要があります。別のパラメータ。

  3. カプセル化が壊れています。 addTrackがインターフェイスにある場合、Trackの内部を知る必要はありません。

  4. 実際には、多くのパラメーターを使用して、2番目の方法でより結合されます。 noパラメータをintからlongに変更する必要があるとします。これは、_MAX_INT_以外のトラックがあるためです(または何らかの理由で)。次に、Trackとメソッドの両方を変更する必要がありますが、メソッドがaddTrack(Track track)の場合は、Trackのみが変更されます。

4つの引数はすべて実際には互いに関連しており、それらの一部は他の結果です。

どちらのアプローチが優れていますか?

16
m3th0dman

さて、最初の3つのポイントは、実際には結合以外の原則に関するものです。常に矛盾する設計原則の間でバランスを取る必要があります。

あなたの4番目のポイントisカップリングについて、そして私はあなたに強く同意します。結合とは、モジュール間のdataの流れに関するものです。データが流れるコンテナーのタイプは、ほとんど重要ではありません。期間をTrackのフィールドとしてではなくdoubleとして渡しても、渡さなくてはなりません。モジュールは同じ量のデータを共有する必要があり、同じ量のカップリングを持っています。

彼はまた、システム内のすべての結合を集合体と見なすことに失敗しています。確かにTrackクラスを導入すると、2つの個別のモジュール間に別の依存関係が追加されますが、ここで重要な測定であるsystemの結合を大幅に減らすことができます。

たとえば、「プレイリストに追加」ボタンとPlaylistオブジェクトを考えてみます。 Trackオブジェクトを導入すると、これら2つのオブジェクトのみを考慮する場合、カップリングが増加すると見なすことができます。これで、2つではなく3つの相互依存クラスができました。ただし、これがシステム全体ではありません。また、トラックのインポート、トラックの再生、トラックの表示なども行う必要があります。そのミックスにクラスを1つ追加しても問題はありません。

次に、ローカルだけでなく、ネットワーク経由でトラックを再生するためのサポートを追加する必要があることを検討してください。同じインターフェースに準拠するNetworkTrackオブジェクトを作成するだけです。 Trackオブジェクトがないと、次のようなあらゆる場所で関数を作成する必要があります。

addNetworkTrack(int no, string title, double duration, URL location)

これにより、カップリングが事実上2倍になり、ネットワーク固有のものを気にしないモジュールであっても、それを渡せるようにするには、それを追跡し続ける必要があります。

リップル効果テストは、実際のカップリング量を決定するための良いテストです。私たちが懸念しているのは、変更が影響する場所を制限することです。

15
Karl Bielefeldt

私の推奨は:

使用する

addTrack( ITrack t )

ただし、ITrackがインターフェイスであり、具象クラスではないことを確認してください。

AlbumはITrackインプリメンターの内部を知りません。 ITrackによって定義されたコントラクトにのみ結合されます。

これは、最小のカップリングを生成するソリューションだと思います。

10

2番目の例のメソッドは、おそらくTrack-オブジェクトをインスタンス化して現在のAlbumオブジェクトに格納するため、increasesカップリングである可能性が高いと主張します。 (上記の私のコメントで示唆されているように、AlbumクラスにはTrackクラスの概念がその内部のどこかにあることが本質的に想定されます。)

最初の例のメソッドは、TrackがAlbumクラスの外部でインスタンス化されることを想定しているため、少なくとも、TrackクラスのinstantiationがAlbumクラスに結合されていないと想定できます。

1つのクラスが2つ目のクラスを参照することは決してないというベストプラクティスが示唆されている場合、オブジェクト指向プログラミングの全体がウィンドウから放棄されます。

4
Derek

結合は、コードで取得しようとする多くの側面の1つにすぎません。カップリングを減らすことで、必ずしもプログラムを改善する必要はありません。一般に、これはベストプラクティスですが、この特定の例では、なぜTrackを知らないのですか?

Trackクラスを使用してAlbumに渡すことにより、コードが読みやすくなりますが、重要なのは、前述のように、パラメーターの静的リストを動的に変換することですオブジェクト。これにより、最終的にインターフェースがより動的になります。

カプセル化は壊れているとおっしゃっていますが、実際はそうではありません。 AlbumTrackの内部を知っている必要があります。オブジェクトを使用しなかった場合、Albumはオブジェクトに渡されるすべての情報を知ってからでないと、オブジェクトを作成できません。すべて同じように使用します。呼び出し元はTrackオブジェクトを作成する必要があるため、Trackの内部も知っている必要がありますが、メソッドに直接渡された場合、呼び出し元はこの情報をすべて知っている必要があります。つまり、カプセル化の利点がオブジェクトの内容を知らない場合、AlbumTrackの情報をまったく同じように利用する必要があるため、この場合はカプセル化を使用できない可能性があります。

Trackを使用したくないのは、呼び出し元にアクセスさせたくない内部ロジックがTrackに含まれている場合です。つまり、Albumが、ライブラリを使用するプログラマが使用するクラスであった場合、Trackを使用して、たとえば、メソッドを呼び出して永続化することを望まないでしょう。データベース上で。これの本当の問題は、インターフェースがモデルに絡まっているという事実にあります。

この問題を修正するには、Trackをインターフェースコンポーネントとロジックコンポーネントに分離し、2つの別個のクラスを作成する必要があります。呼び出し側にとって、Trackは情報を保持し、小さな最適化(計算されたデータやデフォルト値)を提供することを目的とした軽量クラスになります。 Albumの内部では、TrackDAOという名前のクラスを使用して、Trackからデータベースへの情報の保存に関連する重い作業を実行します。

もちろん、これは単なる例です。これはあなたのケースではないと確信しているので、自由にTrack罪悪感を感じないでください。クラスを構築するときは呼び出し元を念頭に置き、必要に応じてインターフェイスを作成することを忘れないでください。

3
Neil

どちらも正しい

addTrack( Track t ) 

better(すでに議論したように)ながら

addTrack( int no, String title, double duration ) 

less結合です。これは、addTrackを使用するコードがTrackクラスがあることを認識する必要がないためです。たとえば、呼び出しコードを更新する必要なく、トラックの名前を変更できます。

より読みやすく保守しやすいコードについて話している間、この記事は カップリング について話している。結合されたコードが少ないほど、実装と理解が必ずしも容易ではありません。

3
k3b

低いカップリングはNoカップリングを意味しません。コードベースの他の場所にあるオブジェクトについて何かを知っている必要があり、「カスタム」オブジェクトへの依存を減らすほど、コードを変更する理由が増えます。あなたが引用している著者が2番目の関数で宣伝しているのは、結合が少ないだけでなく、オブジェクト指向でもありません。これは、GRASPがオブジェクト指向設計方法論であるという全体の考えに反しています。重要なのは、システムをオブジェクトとその相互作用のコレクションとして設計する方法です。それらを回避することは、代わりに自転車に乗るべきだと言って車の運転方法を教えるようなものです。

代わりに、適切な方法は、「疎結合」の理論であるconcreteオブジェクトへの依存を減らすことです。メソッドが知っていなければならない明確な具象型が少ないほど、より良いです。そのステートメントだけで、より単純な型を使用する2番目のメソッドはそれらのより単純な型のすべてを知っている必要があるため、最初のオプションは実際にはあまり結合されていません。確かに組み込みであり、メソッド内のコードは気にする必要があるかもしれませんが、メソッドの署名とメソッドの呼び出し元は、間違いなくnotを行います。概念的なオーディオトラックに関連するこれらのパラメーターの1つを変更すると、それらが個別の場合と、Trackオブジェクト(オブジェクトのポイントであるカプセル化)に含まれている場合とでは、さらに変更が必要になります。

さらに一歩進んで、Trackが同じ仕事をよりよくする何かで置き換えられることが予想された場合、おそらく必要な機能を定義するインターフェースがITrackになるでしょう。これにより、「AnalogTrack」、「CdTrack」、「Mp3Track」など、これらのフォーマットに固有の追加情報を提供しながら、概念的に「トラック」を表すITrackの基本的なデータ公開を提供しながら、異なる実装が可能になります。オーディオの有限のサブピース。 Trackも同様に抽象基本クラスである可能性がありますが、そのためには、Trackに固有の実装を常に使用する必要があります。それをBetterTrackとして再実装すると、予想されるパラメーターを変更する必要があります。

したがって、黄金律。プログラムとそのコードコンポーネントは常に変更する理由があります。新しい何かを追加したり、その動作を変更したりするために、すでに作成したコードを編集する必要があるneverになるプログラムを作成することはできません。あらゆる方法論(GRASP、SOLID、考えられるその他の頭字語または流行語)での目標は、単にwillが時間とともに変化する必要があるものを特定し、それらの変化が起こるようにシステムを設計することですできるかぎり簡単に作成できます(翻訳済み;コード行を最小限に抑え、システムのその他の領域に影響を与え、意図した変更の範囲をできるだけ超えないようにします)。例として、変更される可能性が最も高いのは、トラックが、addTrack()が気にするかどうかに関係なく、より多くのデータメンバーを取得することですnotトラックはBetterTrackに置き換えられます。

3
KeithS