この記事は、保守性の高いコード設計シリーズの一部です。これまでの記事で、凝集度を高めるための様々なテクニックを見てきました。今回はその総仕上げとして、クラス間の「お作法」とも言える重要な設計原則を学びます。
こんにちは。皆さんは、object.getA().getB().getC().doSomething()
のように、数珠つなぎにメソッドを呼び出すコードを書いたことはないでしょうか?これはメソッドチェーンと呼ばれ、時に「汽車事故(Train Wreck)」と揶揄される、危険な設計のサインです。
なぜなら、このようなコードは**デメテルの法則(The Law of Demeter)**という重要な設計原則を破っている可能性が高いからです。
デメテルの法則と「尋ねるな、命じよ」
デメテルの法則は、非常にシンプルに言うと**「直接の友達とだけ話すべき(知らない人と話すべきではない)」**というルールです。オブジェクトは、自分が直接知っているオブジェクトのメソッドしか呼び出すべきではない、とされています。
user.getProfile().getAddress().getCity()
というコードは、「user
さん、あなたのprofile
を教えて。…profile
さん、あなたのaddress
を教えて。…address
さん、あなたのcity
を教えて」と、知らない相手に次々と尋ね回っているようなものです。
これは**「尋ねるな、命じよ (Tell, Don’t Ask)」**という、より本質的な原則にも反しています。オブジェクトからデータ(getProfile()
など)を取り出して外部で処理するのではなく、そのデータをよく知るオブジェクト自身に処理を「命令」すべきなのです。
メソッドチェーンがもたらす問題
【Before】メソッドチェーンで、内部構造に深く依存したコード
// ユーザーの居住都市が送料無料の対象かチェックしたい
class ShippingManager {
void applyFreeShipping(User user) {
// userが持つProfile、Profileが持つAddress...と内部構造を深く知ってしまっている
String city = user.getProfile().getAddress().getCity();
if ("東京".equals(city)) {
// 送料無料の処理
}
}
}
このコードの問題点は、ShippingManager
がUser
クラスだけでなく、Profile
クラスやAddress
クラスの**内部構造にまで密接に依存(密結合)**してしまっていることです。
もし将来、Profile
クラスの仕様が変わり、Address
を持たなくなったとしたらどうでしょう?ShippingManager
のコードは、直接関係ないはずの変更によって、修正を余儀なくされてしまいます。
解決策:オブジェクトに仕事を任せる
この問題を解決するには、ShippingManager
がUser
の内部を詮索するのをやめ、User
自身に「送料無料の対象ですか?」と問い合わせるようにします。
【After】Userクラスに判断を「命令」する、疎結合なコード
// Userクラスに、自分自身に関する判断ロジックを持たせる(高凝集!)
class User {
private final Profile profile;
// ...
// 「送料無料の対象地域に住んでいるか?」を判断する責務を負う
boolean livesInFreeShippingArea() {
String city = profile.getAddress().getCity();
return "東京".equals(city);
}
}
// ShippingManagerは、Userの内部構造を知る必要がなくなる
class ShippingManager {
void applyFreeShipping(User user) {
// Userに「尋ねる」のではなく、「判断して」と「命令」する
if (user.livesInFreeShippingArea()) {
// 送料無料の処理
}
}
}
User
クラスにlivesInFreeShippingArea
という責任あるメソッドを実装しました。これにより、ShippingManager
はUser
の内部構造(Profile
やAddress
を持っていること)を一切知る必要がなくなりました。
User
クラスの内部でどのように居住地を判断しているかは、User
クラス自身の問題です。将来、Address
の持ち方が変わっても、livesInFreeShippingArea
メソッドの振る舞いが同じであれば、ShippingManager
のコードには何の影響もありません。これこそがカプセル化がもたらす恩恵であり、疎結合な設計の力です。
まとめ
長いメソッドチェーンは、オブジェクトのカプセル化が破られ、ロジックが本来あるべき場所から漏れ出している危険なサインです。
- 尋ねるな、命じよ: オブジェクトからゲッターでデータを取り出して処理するのではなく、そのオブジェクト自身に処理を実行させるメソッドを設計しましょう。
- デメテルの法則を守る: オブジェクトは、その内部構造を外部に晒すべきではありません。必要な機能は、責任あるメソッドとして公開しましょう。
これらの原則を守ることで、クラス間の依存関係は適切に管理され、変更に強く、メンテナンスしやすい、真にオブジェクト指向なソフトウェアを構築することができるのです。