8-3
継承による共通化の罠
「処理が似ているから」と基底クラスに共通ロジックを寄せると、最初はコードが減って見える。しかしバリエーションが増えるたびにオーバーライドが重なり、ついには基底クラスがサブクラスの種類を isinstance で調べ始める。基底と派生が互いの事情を知り合う、継承ならではの密結合に行き着く。
蔵書の貸出期間ルール
✕ Bad
class LoanPeriodBase:
def days(self) -> int:
# 基底クラスなのにサブクラスの種類を知っている
if isinstance(self, NewArrivalLoanPeriod):
return 7
if isinstance(self, TeacherLoanPeriod):
return 28
return 14
class NewArrivalLoanPeriod(LoanPeriodBase):
pass # ロジックは基底側に書かれている
class TeacherLoanPeriod(LoanPeriodBase):
pass✓ Good
class LoanPolicy(Protocol):
def days(self) -> int: ...
@dataclass(frozen=True)
class StandardLoan:
def days(self) -> int:
return 14
@dataclass(frozen=True)
class NewArrivalLoan:
def days(self) -> int:
return 7 # 新刊は回転を上げるため短め
@dataclass(frozen=True)
class TeacherLoan:
def days(self) -> int:
return 28Java の abstract class による共通化は、Python では Protocol(構造的部分型)でインターフェースだけ揃えるのが素直。どうしても共通部分があるなら、基底クラスではなく部品クラスに切り出して各ポリシーから委譲する。