条件分岐
6-5

ストラテジーで型チェック分岐を排除

せっかく種類ごとにクラスを分けたのに、呼び出し側で isinstance による型チェック分岐を書いてしまうと、分岐地獄が場所を変えて再発しただけになる。種類によって変わる計算は抽象メソッドとして宣言し、各クラスに実装させれば、呼び出し側は型を一切意識せず同じメソッドを呼ぶだけで済む。

証券口座ごとの譲渡益課税

Bad

口座クラスは分かれているのに、税額計算は呼び出し側の isinstance 分岐に書かれている

isinstance による型チェック分岐
def gain_tax(account: Account, gain: Decimal) -> Decimal:
    if isinstance(account, NisaAccount):
        return Decimal("0")
    elif isinstance(account, SpecificAccount):
        return (gain * Decimal("0.20315")).quantize(Decimal("1"))
    # 法人口座を追加したら、この種の分岐を全部探して直す羽目になる
    raise ValueError("unknown account type")
Good

税額計算を口座クラス自身の責務にする。呼び出し側は型を知らなくてよい

抽象メソッドで各口座に実装を強制
class Account(ABC):
    @abstractmethod
    def gain_tax(self, gain: Decimal) -> Decimal: ...

class NisaAccount(Account):
    def gain_tax(self, gain: Decimal) -> Decimal:
        return Decimal("0")  # 非課税

class SpecificAccount(Account):
    TAX_RATE = Decimal("0.20315")

    def gain_tax(self, gain: Decimal) -> Decimal:
        return (gain * self.TAX_RATE).quantize(Decimal("1"))

# 呼び出し側はどの口座でも同じ1行
tax = account.gain_tax(gain)

ABC の @abstractmethod は「実装し忘れたクラスはインスタンス化できない」という強制力を持つ。新しい口座種別(法人口座など)を追加するとき、gain_tax を書かなければその場でエラーになるので、isinstance 分岐の修正漏れのような静かなバグが構造的に起きない。

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

ストラテジーで型チェック分岐を排除

せっかく種類ごとにクラスを分けたのに、呼び出し側で isinstance による型チェック分岐を書いてしまうと、分岐地獄が場所を変えて再発しただけになる。種類によって変わる計算は抽象メソッドとして宣言し、各クラスに実装させれば、呼び出し側は型を一切意識せず同じメソッドを呼ぶだけで済む。

証券口座ごとの譲渡益課税

Bad

口座クラスは分かれているのに、税額計算は呼び出し側の isinstance 分岐に書かれている

isinstance による型チェック分岐
def gain_tax(account: Account, gain: Decimal) -> Decimal:
    if isinstance(account, NisaAccount):
        return Decimal("0")
    elif isinstance(account, SpecificAccount):
        return (gain * Decimal("0.20315")).quantize(Decimal("1"))
    # 法人口座を追加したら、この種の分岐を全部探して直す羽目になる
    raise ValueError("unknown account type")
Good

税額計算を口座クラス自身の責務にする。呼び出し側は型を知らなくてよい

抽象メソッドで各口座に実装を強制
class Account(ABC):
    @abstractmethod
    def gain_tax(self, gain: Decimal) -> Decimal: ...

class NisaAccount(Account):
    def gain_tax(self, gain: Decimal) -> Decimal:
        return Decimal("0")  # 非課税

class SpecificAccount(Account):
    TAX_RATE = Decimal("0.20315")

    def gain_tax(self, gain: Decimal) -> Decimal:
        return (gain * self.TAX_RATE).quantize(Decimal("1"))

# 呼び出し側はどの口座でも同じ1行
tax = account.gain_tax(gain)

ABC の @abstractmethod は「実装し忘れたクラスはインスタンス化できない」という強制力を持つ。新しい口座種別(法人口座など)を追加するとき、gain_tax を書かなければその場でエラーになるので、isinstance 分岐の修正漏れのような静かなバグが構造的に起きない。

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