1. 概要(Overview)
Change Bidirectional Association to Unidirectional は、
互いを参照している2つのクラスの関連を、必要な方向だけ 残してシンプルにするリファクタリングです。
目的は以下の通りです:
- 不要な結合を解いて、設計を簡素化
- 同期や循環参照による バグ・複雑性・メモリリーク のリスクを低減
- データ取得や遷移の経路を明確化
2. 適用シーン(When to Use)
- 片側の参照が ほとんど使われていない/価値が薄い
- 双方向同期(add/remove、set)が煩雑で、整合性バグ が起こる
- ORマッパーやシリアライズ時に 循環参照 が問題になる
- 集約境界(DDD)を見直した結果、所有方向が明確 になった
よくある匂い:
- Inappropriate Intimacy(不適切な親密さ)
- Shotgun Surgery(散弾銃のような変更)
- Cyclic Dependencies(循環依存)
3. 手順(Mechanics / Steps)
- 使われていない(価値が低い)方向を特定
- その参照/コレクションを 非推奨(@Deprecated) にして呼び出し箇所を置き換え
- 同期メソッド(add/remove/set で相互更新している箇所)を片方向用に簡素化
- 置き換えが完了したら、不要な参照・同期コードを削除
- テスト&静的解析で影響を確認(循環が消えたこと、取得経路が妥当なこと)
4. Kotlin 例(Before → After)
Before:双方向の関連(Customer ↔ Order)
class Customer(val name: String) {
private val _orders = mutableListOf<Order>()
val orders: List<Order> get() = _orders
fun addOrder(order: Order) {
if (!_orders.contains(order)) {
_orders.add(order)
order.setCustomer(this)
}
}
fun removeOrder(order: Order) {
if (_orders.remove(order)) {
order.clearCustomer(this)
}
}
}
class Order(val id: Int) {
private var _customer: Customer? = null
val customer: Customer? get() = _customer
fun setCustomer(customer: Customer) {
if (_customer != customer) {
_customer = customer
customer.addOrder(this)
}
}
fun clearCustomer(customer: Customer) {
if (_customer == customer) {
_customer = null
}
}
}
- 双方向同期のためのガードや相互呼び出しが複雑。
- シリアライズ/ORM で循環が問題になることがある。
After:一方向(Order → Customer のみに簡素化)
class Customer(val name: String)
// 顧客は注文を持たない(必要ならリポジトリ/クエリで取得)
class Order(val id: Int, var customer: Customer)
-
Customer.ordersと相互同期ロジックを削除 -
「注文から顧客へ」という 業務で自然な所有方向 だけ維持
-
もし「顧客の注文一覧」が必要なら、リポジトリやクエリ で取得する:
interface OrderRepository { fun findByCustomer(customer: Customer): List<Order> }
5. 効果(Benefits)
- 相互同期の排除で コードが大幅に簡潔、バグ要因が減る
- 循環参照が消え、シリアライズ/キャッシュ/ORM マッピング が安定
- 集約の所有方向が明確になり、結合度が低下
- テスト容易性・変更容易性の向上
6. 注意点(Pitfalls)
- 本当に片方向で足りるかを事前に確認(アクセスパターン・集約境界)
- 既存コードが
customer.ordersに依存しているなら、段階的移行(@Deprecated と置換)を行う - 取得コストが増える場合は、リポジトリ/読み取り用クエリ、必要に応じて 読み取りモデル(CQRS 的分離) を検討
- ORマッパー使用時は マッピング定義の見直し(外部キーの所有側)を忘れない
まとめ
- Change Bidirectional Association to Unidirectional は「不要な片側参照を捨てて、必要な方向だけ残す」リファクタリング。
- 判断基準:どちらの方向が業務的な所有・遷移として自然か?
- 基本思想:結合を削り、取得はクエリで解決——モデルはシンプルに、読み取りは賢く。