5-2
初期化ロジックは生成側に集める
コンストラクタを公開したままにすると、「どんな値で初期化すべきか」という知識が呼び出し側に散らばる。料金改定のような仕様変更で全呼び出し箇所を探し回ることになる。目的別のファクトリメソッドを用意して、生成の知識をクラス側に集める。
確定申告の基本報酬の初期化
✕ Bad
# 申込フォーム側のモジュール
def create_application(client_name: str) -> Application:
fee = TaxReturnFee(88_000) # 個人向け基本報酬…のつもり
return Application(client_name, fee)
# 見積画面側のモジュール(別ファイル)
def build_estimate(client_name: str) -> Estimate:
fee = TaxReturnFee(88_000) # 同じ知識の重複。料金改定で片方だけ直し忘れる
return Estimate(client_name, fee)✓ Good
from dataclasses import dataclass
from typing import ClassVar
@dataclass(frozen=True)
class TaxReturnFee:
amount: int
_INDIVIDUAL: ClassVar[int] = 88_000
_CORPORATION: ClassVar[int] = 165_000
def __post_init__(self) -> None:
if self.amount <= 0:
raise ValueError("報酬額は正の値にしてください")
@classmethod
def for_individual(cls) -> "TaxReturnFee":
return cls(cls._INDIVIDUAL)
@classmethod
def for_corporation(cls) -> "TaxReturnFee":
return cls(cls._CORPORATION)Python にはコンストラクタを private にする仕組みがないので、「生成はファクトリメソッドから」という規約を @classmethod と _ 付き定数で表現する。生成パターンが増えすぎてきたら、専用のファクトリクラスへの分離を検討する。