オブジェクト指向設計における疎結合


16

私はGRASPを学ぼうとしていますが、これは低結合について説明されていますここ3ページにあります)。

クラスのメソッドaddTrackについて考えてみましょうAlbum。2つの可能なメソッドは次のとおりです。

addTrack( Track t )

そして

addTrack( int no, String title, double duration )

どの方法で結合が減少しますか?Albumクラスを使用するクラスはTrackクラスを知る必要がないので、2番目のものはそうします。一般に、メソッドへのパラメーターは、java。*パッケージの基本型(int、char ...)およびクラスを使用する必要があります。

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

  1. メソッドのパラメーターはできるだけ少ないほうが常に良いです(Uncle BobのClean Codeによれば、なしまたは1つ、できれば2、場合によっては3、特別な場合は3、3つ以上はリファクタリングが必要です-これらはもちろん推奨ルールではありません) 。

  2. 場合addTrackのインタフェースの方法であり、要件がいることを必要とするTrackより多くの情報(たとえば年またはジャンル)を持っている必要があり、インターフェイスを変更する必要があるので、この方法は、別のパラメータがサポートする必要があること。

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

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

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

どのアプローチが良いですか?


2
これは、教授またはトレーナーによってまとめられた文書ですか?あなたが提供したリンクのURLに基​​づいて、それがクラス用であるように見えますが、誰がそれを作成したかについてのドキュメントにはクレジットが表示されません。これがクラスの一部である場合、ドキュメントを提供した人にこれらの質問をすることをお勧めします。ところで、私はあなたの推論に同意します-AlbumクラスがTrackクラスについて本質的に知りたいと思うことは、私には明らかなようです。
デレク

正直なところ、「ベストプラクティス」について読むたびに、それらを一粒の塩で取ります。
-AraK

@Derek「グラップパターンの例」をGoogleで検索してドキュメントを見つけました。誰が書いたのかはわかりませんが、大学からのものだったので、信頼できると思います。私は与えられた情報に基づいて、ソースを無視した例を探しています。
m3th0dman

4
@ m3th0dman「しかし、大学からだったので、信頼できると信じています。」私にとっては大学からのものであるため、信頼できないと考えています。私は、ソフトウェア開発のベストプラクティスについて話している複数年のプロジェクトに取り組んでいない人を信頼していません。
-AraK

1
@AraK Reliableは、疑う余地のないことを意味しません。それが私がここでやろうとしていることです。
m3th0dman

回答:


15

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

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

彼はまた、システム内のすべてのカップリングを集合体と見なしていない。Trackクラスを導入すると、2つの個々のモジュール間に別の依存関係が追加されますが、システムのカップリングを大幅に減らすことができます。これはここでの重要な手段です。

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

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

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

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

リップル効果テストは、実際の結合量を判断するのに適したテストです。私たちが懸念しているのは、変更が影響する場所を制限することです。


1
+プリミティブへのカップリングは、スライス方法に関係なく、カップリングのままです。
JustinC

URLオプションの追加/波及効果について言及する場合は+1。
user949300

4
これに関する+1の興味深い読み物は、プリミティブ型の使用が実際に修正としてのValue Objectを持つプリミティブな強迫観念の「匂い」として実際に見られる野生のDIPの Dependency Inversion Principleの議論です。私には、プリミティブ型のギャグ全体を追跡するTrackオブジェクトを渡す方が良いように思えます...そして、特定のクラスへの依存/結合を回避したい場合は、インターフェースを使用します。
マルジャンヴェネマ

トータルシステムカップリングとモジュールカップリングの違いについてうまく説明しているため、回答を受け入れました。
m3th0dman

10

私の推奨事項は次のとおりです。

使用する

addTrack( ITrack t )

しかし、それがITrack具体的なクラスではなく、インターフェイスであることを確認してください。

AlbumはITrack実装者の内部を知りません。で定義されたコントラクトにのみ結合されますITrack

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


1
Trackは単なるフィールド/ゲッター/セッターのみを持つ単純なBean /データ転送オブジェクトであると信じています。この場合、インターフェイスは必要ですか?
m3th0dman

6
必須?おそらくない。示唆に富む、はい。トラックの具体的な意味は進化する可能性がありますが、消費クラスが必要とするものはおそらく進化しません。
JustinC

2
@ m3th0dmanコンクリートではなく、常に抽象化に依存します。それはTrack愚かであるかスマートであるかに関係なく適用されます。Track結石です。ITrackインターフェイスは抽象化です。そうすることで、将来、さまざまな種類のトラックを使用できるようになりますITrack
Tulainsコルドバ

4
私はその考えに同意しますが、「I」という接頭辞を失います。Robert MartinのClean Codeから、24ページ:「先行するIは、今日のレガシワッドでよくあることですが、気を散らすものであり、最悪の場合は情報が多すぎます。インターフェース。"
ベンジャミンブラムフィールド

1
@BenjaminBrumfieldあなたは正しいです。わかりやすくするために答えを残しますが、プレフィックスも好きではありません。
Tulainsコルドバ

4

2番目の例の方法は、Trackオブジェクトをインスタンス化し、それを現在のAlbumオブジェクトに保存する可能性が高いため、結合を増加させる可能性高いと主張します。(上記の私のコメントで示唆されているように、AlbumクラスにはTrackクラスの概念がその中のどこかにあるということが本質的であると思います。)

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

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


別のクラスへの暗黙的な参照があると、明示的な参照を持つよりもどのように結合されるのかわかりません。いずれにしても、2つのクラスは結合されています。カップリングを明示的にする方が良いと思いますが、どちらの方法でも「もっと」カップリングされるとは思いません。
TMN

1
@TMN、追加のカップリングは、2番目の例がおそらく新しいTrackオブジェクトを内部的に作成することになることを暗示しています。オブジェクトのインスタンス化は、さもなければTrackオブジェクトをAlbumオブジェクトのある種のリストに追加する(Single Responsibility Principleを破る)メソッドに結合されています。トラックの作成方法を変更する必要がある場合、addTrack()メソッドも変更する必要があります。これは、最初の例の場合はそうではありません。
デレク

3

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

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

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

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

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

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


3

両方とも正しい

addTrack( Track t ) 

より良いしばらく(すでに引数付きなど)

addTrack( int no, String title, double duration ) 

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

より読みやすい/維持可能なコードについて話している間、記事はカップリングについて話している。結合が少ないコードは、実装と理解が必ずしも容易ではありません。


引数4を参照してください。2番目の結合がどのように結合されていないかはわかりません。
m3th0dman

3

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

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

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

したがって、黄金律。プログラムとそのコードコンポーネントには、常に変更する理由があります。新しいものを追加したり、その動作を変更したりするために、すでに作成したコードを編集する必要のないプログラムを作成することはできません。あなたの目標は、任意の方法で(GRASP、SOLID、他の頭字語またはあなたが考えることができる流行語は)物事を識別するだけであるだろう、時間とともに変化する必要があるが、それらの変更を可能とすることが容易となるようにシステムを設計します(翻訳済み。コードの数行に触れ、意図した変更の範囲を超えてシステムの他の領域にできるだけ影響を与えません)。適切な場合、最も変更される可能性が高いのは、トラックが、addTrack()が気にするかもしれない、気にしないかもしれないより多くのデータメンバーを獲得することです。 そのトラックはBetterTrackに置き換えられます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.