1. 概要(Overview)
Encapsulate Collection は、クラスが持つコレクション(List, Set, Map など)を
直接公開せず、読み取り専用ビューを返し、追加・削除は明確なメソッド に限定するリファクタリングです。
目的:
- コレクションへの変更経路を制御し、不変条件を保つ
- 外部からの勝手な
add/remove/clearを防ぐ - 変更時の副作用(イベント発火、検証、整合性維持)を一元化
2. 適用シーン(When to Use)
- フィールドに
MutableList/MutableSetをそのまま公開している - クラス外で自由に
add/removeされ、整合性バグ が生じている - 変更時に 検証/ログ/イベント を差し込みたい
- 取得側がコレクションを 直接変更してしまう恐れ がある
よくある匂い:
- Mutable Data(無制限な可変データ)
- Inappropriate Intimacy(過度な親密さ)
3. 手順(Mechanics / Steps)
- 既存の公開コレクションを private バッキング に変更
- パブリックには 読み取り専用ビュー を返す(
List,Set,Map) - 変更操作は
addXxx/removeXxx/clearXxx等の 明示メソッド を用意 - 既存呼び出しを置き換え、外部の直接変更を禁止
- 必要に応じて変更時の 検証/通知/双方向整合性 を実装
4. Kotlin 例(Before → After)
Before:コレクションを丸出し公開
class Order {
val items: MutableList<String> = mutableListOf()
}
fun main() {
val order = Order()
order.items.add("A") // 外部から自由に変更
order.items.clear() // クリティカルな変更も可能
}
- どこからでも変更でき、不変条件を守れない。
- 将来、追加時に検証や在庫確認を入れたくても制御できない。
After①:読み取り専用ビュー + 明示メソッド
class Order {
private val _items = mutableListOf<String>()
val items: List<String> get() = _items // 読み取り専用
fun addItem(name: String) {
require(name.isNotBlank()) { "item name is required" }
_items.add(name)
// 例:在庫確認・イベント発火などをここに集約
}
fun removeItem(name: String) {
_items.remove(name)
}
fun clearItems() {
_items.clear()
}
}
fun main() {
val order = Order()
// order.items.add("X") // コンパイル不可(List には add がない)
order.addItem("A")
println(order.items) // [A]
}
→ 変更の出入口が Order 内に限定され、検証や副作用を一元管理できる。
After②:双方向整合性(所有側でのみ変更)
class Customer(val name: String) {
private val _orders = mutableListOf<Order>()
val orders: List<Order> get() = _orders
fun addOrder(order: Order) {
if (order.customer !== this) order.setCustomer(this)
if (!_orders.contains(order)) _orders.add(order)
}
internal fun removeOrder(order: Order) {
_orders.remove(order)
}
}
class Order(val id: Int) {
var customer: Customer? = null
private set
fun setCustomer(newCustomer: Customer) {
customer?.removeOrder(this) // 旧所有者から除去
customer = newCustomer
newCustomer.addOrder(this) // 新所有者へ登録(片側のみ公開)
}
}
→ 追加・削除を 所有側 API に集約し、整合性が崩れない ようにする。
5. 効果(Benefits)
- コレクションの不変条件(重複禁止、サイズ制限、整合性)を 一か所で強制
- 変更イベント・ログ・監査を 中央集約 で差し込める
- 外部からの予期せぬ変更がなくなり、バグを大幅に減少
- API が「読めば意図がわかる」自己文書化 に近づく
6. 注意点(Pitfalls)
- パフォーマンス要件が厳しい場合、ビュー生成やコピーに注意
- 読み取り専用ビューを返しても、内部参照を漏らすと意味がない
- 例:
return _items(Mutable をそのまま返す)は厳禁
- 例:
- 変更 API は ドメイン操作に即した名前 を付与(
addItemではなくaddLineItem(product, qty)など) - 並行アクセスでは スレッドセーフティ(同期・不変コレクション)を検討
まとめ
- Encapsulate Collection は「コレクションを外に晒さず、変更は意図のあるメソッドだけで行わせる」リファクタリング。
- 判断基準:このコレクションは外から勝手に変えられてよいか?
- 基本思想:読み取りは公開・変更は統制。整合性と副作用の起点をひとつに集める。