クラスAがあるとします。
public class A{
public void a(){
}
}
そしてクラスBはAを使用します:
public class B{
private A a;
}
AをBにカプセル化する必要があります。
public class B{
private A a;
public void a(){
a.a();
}
}
その後
new B().a();
または、Bのユーザーに直接Aにアクセスさせる:
public class B{
private A a;
public A getA(){
return A;
}
}
その後
new B().getA().a();
?
多くの人が新しいB()。a();を好むと思います。 BのユーザーがAについて知る必要がないためです。しかし、少なくとも1つの欠点があります。Aに新しいメソッドを追加すると、AとBの両方を変更する必要があります。直接、それから私はBを修正する必要はなく、ただ:
new B().getA().a();
new B().getA().newMethod();
メソッドとしてAをBにカプセル化する必要がありますか?それとも、BのユーザーにAに直接アクセスさせるだけですか?
bothは避けてください。それらは両方ともそれ自体で(少なくとも説明された方法で)においです。
最初のオプション(説明したとおり)は、(すべてのif)である場合、すべてのメソッドを複製することが本当に必要な匂いですA
はB
で公開されます。クラスB
は、何かを行うために存在する必要があります。 B
がA
のパブリックインターフェイスを複製する場合(同じインターフェイスを実装せずに)それは私には思えます、そのB
は実際には何もしません(またはそれがすることおよびはA
にインターフェイスを公開します-つまりB
は複数のことを行います。とにかく、A
とB
の両方が同じ基本クラスまたはインターフェイスから派生した場合、それは別の話です。 B
は、追加機能を実装するデコレータになる可能性があります。たとえば、A.a()
への呼び出しをログに記録します。 B.a()
はログメッセージを発行し、A.a()
を呼び出してから、別のログメッセージを発行することができます。
2番目のオプション(B.a
公的に)は多くの人々によって悪い考えと考えられています。とにかく、例外があります。 A
が単なるデータクラスである場合(ただし、必ずしもそうである必要はありません)、問題ないと考えられます。
どちらの場合でも、B
がA
を操作する独自のパブリックインターフェイスを提供すると、よりクリーンであると見なされます。
Law of Demeter は、バージョン1を使用する必要があると述べています。
Don't_repeat_yourself 、一般的なLEANガイドライン、バージョン2を使用すると言うため、新しいメソッドをAに追加するときは、B(およびC、D ...)ではなく、一度だけ追加する必要があります。ソフトウェアの原則の良いところは、多くの選択肢があることです。 :-(
私の見解:依存している
AがListやStreamなどの抽象的で堅牢でよく理解されているクラスである場合は、バージョン2を使用して、ユーザーが直接接続できるようにします。彼らはAへのインターフェースをすでに知っています、それを非表示にしても何も得られません。実用的です。
しかし、Aが独自の複雑で乱雑なベータ版のホームビルドクラスである場合は、デメテルの法則を使用してアクセスを制限します。
厳格なルールはありません。 generalでは、A
をB
の後ろにカプセル化し、必要に応じて機能とデータのみを公開します。 B
のコンテキスト。
それはgeneralにあります。
実際には、オブジェクトFoo
がオブジェクトBar
で構成され、pragmaticソリューションがBar
をpublic
メンバーとして。しかし、その決定は多くの事柄に依存するため、ケースバイケースで行う必要があります。
これは結局、モデリングの質問に要約されます。したがって、Foo
(またはB
)がモデル化している内容に応じて、異なる答えが得られます。
TLDR:カプセル化は素晴らしいガイドラインですが、実用的であることを忘れないでください。
B
のドキュメントを書くことを想像してみてください。 A
がそのドキュメントで自然に直接参照できる場合(構築に関する部分以外)、2番目のオプションを使用する必要があります。ドキュメントがA
実装の詳細のみを参照する場合( "舞台裏でA
to ...")を使用する場合は、最初のオプションを使用する必要があります。
たとえば、A
がPerson
であり、B
がDepratment
であるとします。ドキュメントでは、部門にマネージャーがいると言えます。これは内部実装の詳細ではなく、Department
の公式および外部定義の一部です!したがって、department.getManagerName()
のようなコードは表示したくありません。部門にはマネージャ名しかない-マネージャオブジェクト全体がAPIの一部である-department.getManager().getName()
が必要です。これは、vacation.addParticipant(department.getManager())
のようなこともできることを意味します。これは、最初のアプローチ(department.sendManagerToVacation(vacation)
ugh ...)では不自然に見えます。
別の例-A
はTCPConnection
であり、B
はFooReader
であり、TCP接続からエンコードされたFoo
sを読み取ってFoo
オブジェクトを作成するとします。 FooReader
のドキュメントでは、TCPConnection
をどのように参照しますか?おそらく実装の詳細として( "FooReader
はTCPConnection
"から読み取ります)、またはそれが構築について話しているとき( "new FooReader(tcpConnection)
は、TCP接続からFoo
sを読み取ります ")。 TCP接続は、Foo
リーダーAPIの一部ではありません。FooReader
オブジェクトを取得した場合、Foo
sを読み取ることだけを知っていれば、どこから読み取ってもかまいません。したがって、ここでは最初のオプションを使用し、たとえばfooStream.hasMore()
ではなくfooStream.getTcpConnection().hasMore()
を使用します。ユーザーは、TCP接続にさらに多くの値があるかどうかは気にしません-読み取る必要があるFoo
がさらにあるかどうかだけが気になります。
Bの目的は何ですか?
BはすべてのAメソッドを模倣しているが、変更せずにAにいくつかの機能を追加するだけのデコレータですか?
AとBは、デコレータパターンの要件である同じインターフェイスを実装していないように見えるため、質問では明確ではありません。しかし、そうであれば、オプション1は非常に有効なオプションです。
Aは、特定の関数を委任するためにBに注入された依存関係だけですか?
そのような場合、Bは通常すべてのメソッドをAでラップしませんが、通常は内部で、いくつかのAメソッドを呼び出すだけです。これは、実際にコードを変更せずにBの機能を変更する別の形式です。
Aは単なるBのメンバーですか?
その場合、コンシューマーがAの状態を変更する可能性があるため、オプション2の使用には注意する必要があります。それでいいの? Bは複数のスレッドで使用されますか? (もしそうならあなたは追加の考慮が必要になるでしょう)。
ご覧のとおり、Aをカプセル化する理由は、それを公開する理由よりも多くあります。
デメテルの法則(知識不足の原則とも呼ばれます) のようなオプション2に対するいくつかの原則、カプセル化のまさに概念、 Train Wreck と呼ばれるアンチパターンがあります。
知識の少ない原理のデメテルの法則:
- 各ユニットは、他のユニットについて限られた知識しか持っていない必要があります。現在のユニットに「密接に」関連しているユニットのみです。
- 各ユニットは、その友達とのみ会話する必要があります。見知らぬ人と話をしないでください。
- 直接の友達とのみ話してください。
Train Wreckアンチパターン:
トレインレックアンチパターンは、一連のメソッド呼び出しがすべて1行のコードで互いに追加されたときに発生します。一連のメソッド呼び出しを読むのに多くの時間を費やしていて、コード行が実際に何をしているかを理解しようとしていることに気付いた場合、おそらく列車事故に対処していることになります。