3秒でわかる
データとそれを扱う操作をひとまとめにし、外から触れる窓口を絞る設計の考え方。壊れた状態になり得ない部品を作るために使います。
もう少し詳しく
どういうものか
カプセル化は、データとそれを扱う手続きを 1 つのまとまりに閉じ込め、外部に見せる部分を必要最小限に絞る設計の考え方です。オブジェクト指向の中心にある原則で、フィールドを private にして公開メソッド経由でのみ操作させる形が典型です。
大事なのは「隠すこと」自体ではなく、「外に出す窓口を自分で選ぶこと」です。窓口が絞られていれば、その内側は自由に書き換えられます。
なぜ必要か
内部の値に誰でも代入できると、その値が満たすべき条件をコードのどこでも壊せます。「合計は必ず明細の和と一致する」「日付は開始が終了より前」といった約束は、代入箇所が 10 か所あれば 10 か所で守らねばなりません。窓口を 1 つに絞れば、検査もその 1 か所で済みます。
保守の面でも効きます。内部でリストを使っていた実装をマップに変えたいとき、外部がリストを直接参照していれば全ての利用箇所が壊れます。メソッド越しにしか触れられなければ、差し替えはクラスの中で完結します。
具体例
class ShoppingCart:
def __init__(self):
self._items = {} # 外から直接いじらせない
def add(self, name, price, qty=1):
if qty <= 0:
raise ValueError("数量は 1 以上にしてください")
item = self._items.get(name, {"price": price, "qty": 0})
item["qty"] += qty
self._items[name] = item
@property
def total(self):
return sum(i["price"] * i["qty"] for i in self._items.values())
@property
def items(self):
return dict(self._items) # 複製を返す
cart = ShoppingCart()
cart.add("コーヒー豆", 1200, 2)
print(cart.total) # 2400items が複製を返している点が要点です。内部の辞書をそのまま返すと、受け取った側が中身を書き換えられ、隠した意味が消えます。
つまずきやすいところ
全フィールドに getter と setter を機械的に生やす形は、カプセル化の見た目だけを真似た状態です。setBalance が無検査で公開されているなら、フィールドを公開しているのと同じです。
Python では _name は慣習上の非公開でしかなく、外から代入できてしまいます。言語が強制しないぶん、公開する窓口を意識して設計する責任が書き手に残ります。
似た用語との違い
| 用語 | 内容 |
|---|---|
| カプセル化 | 内部を隠し、操作の窓口を絞る設計方針 |
| 情報隠蔽 | 設計上の決定を外から見えなくすること。カプセル化の目的側 |
| 抽象化 | 本質だけを取り出して単純化すること |
覚え方
自動販売機には投入口とボタンしかなく、内部の在庫棚に手は届きません。使う側が壊しようのない形にするのがカプセル化です。