密結合
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

継承をやめ、ルールごとに独立したクラスを Protocol で揃える

独立した貸出ポリシー
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 28

Java の abstract class による共通化は、Python では Protocol(構造的部分型)でインターフェースだけ揃えるのが素直。どうしても共通部分があるなら、基底クラスではなく部品クラスに切り出して各ポリシーから委譲する。

参考: 『良いコード/悪いコードで学ぶ設計入門』(ミノ駆動 著、技術評論社)第8章。コード例は原則を自分の題材で表現し直したオリジナル。
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

継承をやめ、ルールごとに独立したクラスを Protocol で揃える

独立した貸出ポリシー
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 28

Java の abstract class による共通化は、Python では Protocol(構造的部分型)でインターフェースだけ揃えるのが素直。どうしても共通部分があるなら、基底クラスではなく部品クラスに切り出して各ポリシーから委譲する。

参考: 『良いコード/悪いコードで学ぶ設計入門』(ミノ駆動 著、技術評論社)第8章。コード例は原則を自分の題材で表現し直したオリジナル。