はじめに
メッセージチェーン(Message Chains) とは、
オブジェクトの内部構造に深く依存している呼び出し を指します。
例:
order.customer.address.city
呼び出し元が order → customer → address → city と、
ドットが連鎖して内部構造をたどる ことで、オブジェクト間の結合度が高くなります。
→ 内部構造の変更が呼び出し元にまで波及し、保守性を損なう悪臭です。
26.1 特徴
- ドットが連鎖している(
obj.a().b().c()形式) - 呼び出し元が他クラスの内部構造を意識している
- 内部モデルを少し変えると呼び出し元をすべて修正しなければならない
- 「火星まで届くドットチェーン(train wreck)」と呼ばれることもある
26.2 解決手法
-
メソッド移動(Move Method)
→ 処理を適切なクラス側に移動 -
カプセル化(Encapsulate)
→ 内部構造をたどらずに済むよう、外部向けのメソッドを提供 -
委譲の隠蔽(Hide Delegate)
→ 呼び出し元が仲介クラスを直接知らないようにする -
ファサード(Facade Pattern)
→ 複雑な呼び出しをまとめて扱える API を用意
26.3 Kotlin 例
Before:メッセージチェーンが露出
class Customer(val address: Address)
class Address(val city: String)
class Order(val customer: Customer)
fun printOrderCity(order: Order) {
// 内部構造に強く依存
println(order.customer.address.city)
}
-
Order → Customer → Address → Cityのチェーン依存 -
CustomerやAddressの構造変更がprintOrderCityに影響
After①:委譲を隠蔽
class Customer(private val address: Address) {
fun getCity(): String = address.city
}
class Order(private val customer: Customer) {
fun getCustomerCity(): String = customer.getCity()
}
fun printOrderCity(order: Order) {
println(order.getCustomerCity())
}
→ 呼び出し元は order.getCustomerCity() だけでよい。内部構造を意識しなくて済む。
After②:処理を適切なクラスへ移動
class Customer(private val address: Address) {
fun livesIn(city: String): Boolean = address.city == city
}
class Order(private val customer: Customer) {
fun isFrom(city: String): Boolean = customer.livesIn(city)
}
fun filterOrdersFromTokyo(orders: List<Order>): List<Order> =
orders.filter { it.isFrom("Tokyo") }
→ 「どの都市の注文か?」というロジックを Order に移すことで、
呼び出し元は内部を意識せずに利用可能。
26.4 実務での指針
- ドットが3つ以上 つながっていたら要注意
- 呼び出し元が内部構造に依存していないかを確認
- Hide Delegate / Move Method / Facade を駆使して依存を局所化
- 特に Kotlin/Java の DTO/Entity を扱うときに頻出するため注意
まとめ
- Message Chains は「内部構造を外にさらけ出す」悪臭
- 解決策は 委譲の隠蔽 / メソッド移動 / ファサード化
- 基本思想:「呼び出し元は結果だけを知りたい。経路は隠せ」