Python講座 / 継承と合成の使い分け
継承は既存の型と共通の契約を保ちながら振る舞いを拡張する方法で、合成は必要な機能を持つ別のオブジェクトを組み合わせる方法です。台帳が手数料方針を「使う」関係なら、方針を部品として渡す合成が自然です。
型の関係と利用する関係を区別する
子クラスを親クラスの代わりに使っても、入力や結果の約束が成り立つかを考えます。単にコードの数行が似ているという理由だけで継承すると、無関係な機能まで引き継ぎやすくなります。深い階層では親の変更の影響も広がります。
super()はメソッド解決順序に従って次の実装を参照します。常に決まった親クラス名を直接呼ぶ、という意味だけではありません。
オブジェクトの責務と方針のテストは、クラスとインスタンスの基本とunittestで正常値・境界値・失敗をテストするで扱う規則とつながります。
手数料方針を差し替えて実行する
composition.pyへ保存し、python composition.pyを実行します。FixedFeeはFeePolicyを拡張し、Ledgerは渡されたpolicyへ計算を委譲します。
class FeePolicy:
def apply(self, amount):
return amount
class FixedFee(FeePolicy):
def __init__(self, fee):
self.fee = fee
def apply(self, amount):
return super().apply(amount) + self.fee
class Ledger:
def __init__(self, policy):
self.policy = policy
def charge(self, amount):
return self.policy.apply(amount)
print(Ledger(FeePolicy()).charge(8000))
print(Ledger(FixedFee(500)).charge(8000))
予想結果は次のとおりです。同じ8000に対し、既定の方針は8000、固定手数料500の方針は8500を返します。
8000 8500
差し替え可能な部品の契約を決める
Ledgerは手数料計算の内部を知る必要がありません。applyがどんな値を受け、何を返し、失敗をどう伝えるかを共通にすると、別の方針やテスト用の部品へ差し替えられます。
Pythonでは継承せず同じ操作を備えるオブジェクトを使う設計も可能です。ただし同名のメソッドがあるだけで意味まで一致するとは限りません。単位、許される入力、戻り値と例外を文書や型ヒント、テストで揃えます。小さな計算なら関数を渡すだけで十分な場合もあります。
一つの条件を変えて設計を確かめる
固定手数料を0にして、基本方針と同じ結果になるか確認し、500へ戻します。子クラスだけ入力制限が厳しくなっていないか、戻り値の意味が変わっていないかも確認してください。
ネットワーク通信やファイル保存を手数料計算へ混ぜると、純粋な計算の確認が難しくなります。計算と入出力を分離し、階層を増やす前に部品を一つ取り出す設計で十分か検討します。
同じ「金額に手数料方針を適用する」要件を二つの設計で比べます。継承はfeeをオーバーライドし、合成は呼び出し可能なfee_policyを受け取ります。どちらも8,500を返し、合成側の方針だけを交換すると8,000になります。実行中に方針を替えるなら合成、安定したサブタイプ関係が必要なら継承を検討します。両方を組み合わせる設計も可能ですが、この比較では選択基準を分けています。
全体を policies.py に保存し、同じ練習フォルダーで python policies.py を実行します。
class InheritedLedger:
def fee(self):
return 0
def charge(self, amount):
return amount + self.fee()
class FixedLedger(InheritedLedger):
def fee(self):
return 500
class ComposedLedger:
def __init__(self, fee_policy):
self.fee_policy = fee_policy
def charge(self, amount):
return amount + self.fee_policy()
def fixed_fee():
return 500
print(FixedLedger().charge(8000))
ledger = ComposedLedger(fixed_fee)
print(ledger.charge(8000))
ledger.fee_policy = lambda: 0
print(ledger.charge(8000))
予想結果:
8500 8500 8000




