Law of Demeter
直接の友達とだけ話すこと
オブジェクトが、このメソッドを呼び出してもよいのか否か?という、指針となる法則です。
最小知識の原則、ともいいます。
どんなときに使うの?
ドットいっぱい使ってるとき
ふんふーん♪
Console.log(student.getClass().getName())
ドットいっぱいつなげるのダメ!組み合わせ爆発起こるから!
ドットがつながる=組み合わせ爆発
ドットによるメソッド呼び出し
オブジェクト指向プログラミング言語では、ドット(.)はメソッド呼び出しを表します。
objectA.method();
返却されるのは、メソッドの戻り値です。
戻り値がオブジェクトであった場合、そのオブジェクトに対して、またドットで呼び出せます。
objectA.getObjectB().method();
このようにドットをつなげていくことで、オブジェクトがどこにあっても、必要なメソッドを呼び出すことができます。
objectA.getObjectB().getObjectC().getObjectD().method();
ドット連鎖がまねく組み合わせ爆発
ドットをつなげれば、必要なメソッドが呼び出せます。文法的になにも間違っていないし、やっていること分かりやすい。
では、なにが良くないのか。
クラス図で考えます。
クラスAがクラスBを呼び出すとき、クラスAからクラスBへの依存関係が生まれます。クラスAは、クラスBの仕様に依存しているわけです。
呼び出しが、クラスA→クラスB→クラスC となる場合。A→B、B→C はもちろんのこと、A→Cの依存関係も生まれます。クラスAは、クラスBとCに依存しています。
さらに、A→B→C→D の場合は、依存関係がさらに多くなります。クラスAは、クラスB,C,Dのすべてに依存しています。
このような、依存関係が複雑で膨大になった状態が、組み合わせ爆発です。
組み合わせ爆発による問題
依存関係は、依存先に変更が入ると、依存元に影響が及びます。
先ほどの例なら、クラスAは、クラスB、C、Dのいずれの変更でも、影響が及ぶ状態。こうなると、
- たとえ一箇所の変更でも、影響が広範囲に及んでしまう
- ある変更が、思いもよらない箇所に影響を与えてしまう
といった問題を引き起こします。
例えば、クラスDに修正が入った、としましょう。
クラスの関係性から言えば、クラスDは末端です。この修正が、まさか親玉のクラスAに及んでしまうなんて、思いもしないわけです。
でも実際は、クラスDの修正によりクラスAが動かなくなる、ということも、起こり得るわけです。
使えるドットはひとつだけ
デメテル法則による呼び出しルール
では、オブジェクトはどのようなメソッドなら呼び出してよいの?この指針を、デメテル法則が示しています。
- そのオブジェクト自身のメソッド
- オブジェクトが所有するオブジェクトのメソッド
- メソッドの引数で渡されたオブジェクトのメソッド
- メソッドの内部で生成したオブジェクトのメソッド
プログラムで見てみましょう。
class ClassA {
ClassB objectB;
private void funcInner() {
...
}
public void func(ClassC objectC) {
// OK オブジェクト自身のメソッド
this.funcInner();
// OK オブジェクトが所有するオブジェクトのメソッド
this.objectB.func();
// OK メソッドの引数で渡されたオブジェクトのメソッド
objectC.func();
// OK メソッドの内部で生成したオブジェクトのメソッド
ClassD objectD = new ClassD();
objectD.func();
// NG! オブジェクトから取得したオブジェクトのメソッドは呼んではいけない
objectD.getObectE().func();
}
}
これで分かるルールは、「使えるドットはひとつだけ」。
あなたが話せるのは、あなたの直接の友達だけです。あなたの友達の友達には話しかけるな、ということです。
これにより、必要以上の複雑な関連性を避け、安定性の高いプログラムとなります。
「オブジェクト取得メソッド」を避ける
友達の友達に話しかけること。これはプログラムでいえば、
- あるオブジェクトから別のオブジェクトを取得する
というメソッドが、トリガーになっています。
そのようなメソッドを止めて、やりたいことを指示するようなメソッドに置き換える。そうすると、デメテル法則が自然に守られるようになります。
このような、何をすべきかを指示するという考え方を、Tell,Don’t Askと言います。
Tell, Don’t Askの考えて沿って、メソッドを定義しましょう。
メソッドチェーンは例外
ドット連鎖は基本的に避けるべきです。ただし、以下のような例は、例外です。
double average = numbers.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
asyncTask1()
.then(result1 => {
console.log(result1);
return asyncTask2();
})
.then(result2 => {
console.log(result2);
})
.catch(error => {
console.error(error);
});
メソッドチェーン、と呼ばれるテクニックです。
ドット演算子で、自身のオブジェクト(または同種のオブジェクト)をリターンし、メソッドをつなげていきます。オブジェクトに対する機能拡張を、簡潔で読みやすく書けるようにしています。
これは、関連性を拡大しているわけではないので、例外と考えてください。
まとめ
ことプログラミングにおいては、他者との関係性は、できるだけ少ないほうがよいのです。
友達の友達に、いきなり話しかけてお願いするのは、やめましょう。
現実の生活では…友達の友達とも、仲良くできるに越したことはないですね。
コメント