尋ねるな、命じろ
呼び出し側がオブジェクトの状態をいちいち尋ね、判断し、外から書き換える構造は、判断ロジックを呼び出し側の数だけ複製させる。状態を持つオブジェクトに「こうしろ」と命じれば、判断はデータの持ち主に一元化される。あわせて、状態を変えるメソッド(コマンド)と状態を返すメソッド(クエリ)を1つに混ぜないことも大切。
01 督促判定を請求書自身に任せる
def send_reminders(invoices: list[Invoice], today: date) -> None:
for invoice in invoices:
# 状態の判断が Invoice の外にある
if invoice.status == "issued" and invoice.due_date < today:
invoice.status = "overdue"
mailer.send(invoice.customer_email, "お支払いのお願い")class Invoice:
def update_overdue(self, today: date) -> None:
# 期日と状態の判断は請求書自身が知っている
if self._status is Status.ISSUED and self._due_date < today:
self._status = Status.OVERDUE
@property
def is_overdue(self) -> bool:
return self._status is Status.OVERDUE
def send_reminders(invoices: list[Invoice], today: date) -> None:
for invoice in invoices:
invoice.update_overdue(today)
if invoice.is_overdue:
mailer.send(invoice.customer_email, "お支払いのお願い")Java の getter/setter だらけのクラスに相当するのが、Python では「公開属性を外から読んで書き戻すだけのクラス」。属性公開そのものは Python らしい書き方だが、判断を伴う変更はメソッドとして持ち主に置く。
02 コマンドとクエリを分離する
class ClientDeposit:
"""顧客からの預り金"""
def __init__(self) -> None:
self._balance = 0
def deposit_and_balance(self, amount: int) -> int:
self._balance += amount # 変更(コマンド)
return self._balance # 取得(クエリ)も兼ねている
balance = deposit.deposit_and_balance(0) # 照会のつもりの 0 円入金が履歴を汚すclass ClientDeposit:
def __init__(self) -> None:
self._balance = 0
def deposit(self, amount: int) -> None:
if amount <= 0:
raise ValueError("入金額は正の値で指定してください")
self._balance += amount
@property
def balance(self) -> int:
return self._balanceコマンド・クエリ分離(CQS)と呼ばれる原則。「クエリは何度呼んでも安全」「コマンドは状態を変えるが値を返さない」と割り切ると、メソッドの役割が名前から読み取れるようになる。