私は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つ、場合によっては2つ、特別な場合は3つ。3つ以上はリファクタリングが必要です。これらはもちろん、ホリールールではなく推奨事項です) 。
addTrack
がインターフェイスのメソッドであり、Track
に詳細な情報(年やジャンルなど)が必要であるという要件が必要な場合は、インターフェイスを変更して、メソッドがサポートするようにする必要があります。別のパラメータ。
カプセル化が壊れています。 addTrack
がインターフェイスにある場合、Track
の内部を知る必要はありません。
実際には、多くのパラメーターを使用して、2番目の方法でより結合されます。 no
パラメータをint
からlong
に変更する必要があるとします。これは、_MAX_INT
_以外のトラックがあるためです(または何らかの理由で)。次に、Track
とメソッドの両方を変更する必要がありますが、メソッドがaddTrack(Track track)
の場合は、Track
のみが変更されます。
4つの引数はすべて実際には互いに関連しており、それらの一部は他の結果です。
どちらのアプローチが優れていますか?
さて、最初の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倍になり、ネットワーク固有のものを気にしないモジュールであっても、それを渡せるようにするには、それを追跡し続ける必要があります。
リップル効果テストは、実際のカップリング量を決定するための良いテストです。私たちが懸念しているのは、変更が影響する場所を制限することです。
私の推奨は:
使用する
addTrack( ITrack t )
ただし、ITrack
がインターフェイスであり、具象クラスではないことを確認してください。
AlbumはITrack
インプリメンターの内部を知りません。 ITrack
によって定義されたコントラクトにのみ結合されます。
これは、最小のカップリングを生成するソリューションだと思います。
2番目の例のメソッドは、おそらくTrack-オブジェクトをインスタンス化して現在のAlbumオブジェクトに格納するため、increasesカップリングである可能性が高いと主張します。 (上記の私のコメントで示唆されているように、AlbumクラスにはTrackクラスの概念がその内部のどこかにあることが本質的に想定されます。)
最初の例のメソッドは、TrackがAlbumクラスの外部でインスタンス化されることを想定しているため、少なくとも、TrackクラスのinstantiationがAlbumクラスに結合されていないと想定できます。
1つのクラスが2つ目のクラスを参照することは決してないというベストプラクティスが示唆されている場合、オブジェクト指向プログラミングの全体がウィンドウから放棄されます。
結合は、コードで取得しようとする多くの側面の1つにすぎません。カップリングを減らすことで、必ずしもプログラムを改善する必要はありません。一般に、これはベストプラクティスですが、この特定の例では、なぜTrack
を知らないのですか?
Track
クラスを使用してAlbum
に渡すことにより、コードが読みやすくなりますが、重要なのは、前述のように、パラメーターの静的リストを動的に変換することですオブジェクト。これにより、最終的にインターフェースがより動的になります。
カプセル化は壊れているとおっしゃっていますが、実際はそうではありません。 Album
はTrack
の内部を知っている必要があります。オブジェクトを使用しなかった場合、Album
はオブジェクトに渡されるすべての情報を知ってからでないと、オブジェクトを作成できません。すべて同じように使用します。呼び出し元はTrack
オブジェクトを作成する必要があるため、Track
の内部も知っている必要がありますが、メソッドに直接渡された場合、呼び出し元はこの情報をすべて知っている必要があります。つまり、カプセル化の利点がオブジェクトの内容を知らない場合、Album
はTrack
の情報をまったく同じように利用する必要があるため、この場合はカプセル化を使用できない可能性があります。
Track
を使用したくないのは、呼び出し元にアクセスさせたくない内部ロジックがTrack
に含まれている場合です。つまり、Album
が、ライブラリを使用するプログラマが使用するクラスであった場合、Track
を使用して、たとえば、メソッドを呼び出して永続化することを望まないでしょう。データベース上で。これの本当の問題は、インターフェースがモデルに絡まっているという事実にあります。
この問題を修正するには、Track
をインターフェースコンポーネントとロジックコンポーネントに分離し、2つの別個のクラスを作成する必要があります。呼び出し側にとって、Track
は情報を保持し、小さな最適化(計算されたデータやデフォルト値)を提供することを目的とした軽量クラスになります。 Album
の内部では、TrackDAO
という名前のクラスを使用して、Track
からデータベースへの情報の保存に関連する重い作業を実行します。
もちろん、これは単なる例です。これはあなたのケースではないと確信しているので、自由にTrack
罪悪感を感じないでください。クラスを構築するときは呼び出し元を念頭に置き、必要に応じてインターフェイスを作成することを忘れないでください。
どちらも正しい
addTrack( Track t )
はbetter(すでに議論したように)ながら
addTrack( int no, String title, double duration )
はless結合です。これは、addTrack
を使用するコードがTrack
クラスがあることを認識する必要がないためです。たとえば、呼び出しコードを更新する必要なく、トラックの名前を変更できます。
より読みやすく保守しやすいコードについて話している間、この記事は カップリング について話している。結合されたコードが少ないほど、実装と理解が必ずしも容易ではありません。
低いカップリングはNoカップリングを意味しません。コードベースの他の場所にあるオブジェクトについて何かを知っている必要があり、「カスタム」オブジェクトへの依存を減らすほど、コードを変更する理由が増えます。あなたが引用している著者が2番目の関数で宣伝しているのは、結合が少ないだけでなく、オブジェクト指向でもありません。これは、GRASPがオブジェクト指向設計方法論であるという全体の考えに反しています。重要なのは、システムをオブジェクトとその相互作用のコレクションとして設計する方法です。それらを回避することは、代わりに自転車に乗るべきだと言って車の運転方法を教えるようなものです。
代わりに、適切な方法は、「疎結合」の理論であるconcreteオブジェクトへの依存を減らすことです。メソッドが知っていなければならない明確な具象型が少ないほど、より良いです。そのステートメントだけで、より単純な型を使用する2番目のメソッドはそれらのより単純な型のすべてを知っている必要があるため、最初のオプションは実際にはあまり結合されていません。確かに組み込みであり、メソッド内のコードは気にする必要があるかもしれませんが、メソッドの署名とメソッドの呼び出し元は、間違いなくnotを行います。概念的なオーディオトラックに関連するこれらのパラメーターの1つを変更すると、それらが個別の場合と、Trackオブジェクト(オブジェクトのポイントであるカプセル化)に含まれている場合とでは、さらに変更が必要になります。
さらに一歩進んで、Trackが同じ仕事をよりよくする何かで置き換えられることが予想された場合、おそらく必要な機能を定義するインターフェースがITrackになるでしょう。これにより、「AnalogTrack」、「CdTrack」、「Mp3Track」など、これらのフォーマットに固有の追加情報を提供しながら、概念的に「トラック」を表すITrackの基本的なデータ公開を提供しながら、異なる実装が可能になります。オーディオの有限のサブピース。 Trackも同様に抽象基本クラスである可能性がありますが、そのためには、Trackに固有の実装を常に使用する必要があります。それをBetterTrackとして再実装すると、予想されるパラメーターを変更する必要があります。
したがって、黄金律。プログラムとそのコードコンポーネントは常に変更する理由があります。新しい何かを追加したり、その動作を変更したりするために、すでに作成したコードを編集する必要があるneverになるプログラムを作成することはできません。あらゆる方法論(GRASP、SOLID、考えられるその他の頭字語または流行語)での目標は、単にwillが時間とともに変化する必要があるものを特定し、それらの変化が起こるようにシステムを設計することですできるかぎり簡単に作成できます(翻訳済み;コード行を最小限に抑え、システムのその他の領域に影響を与え、意図した変更の範囲をできるだけ超えないようにします)。例として、変更される可能性が最も高いのは、トラックが、addTrack()が気にするかどうかに関係なく、より多くのデータメンバーを取得することですnotトラックはBetterTrackに置き換えられます。