🚀 ニフティ’s Notion

継承と委譲

継承
  • すでに定義されたクラスの機能を引き継いで新たなクラスを作る、クラスの再利用性を高める考え方。
    • 継承元のクラスは親クラスやスーパークラス、継承してできた新しいクラスを子クラスやサブクラスと呼ぶ。
    • 子クラスは親クラスのデータやメソッドを使用できる。
  • 複数のクラスを継承できるプログラミング言語と単一のクラスしか継承できないプログラミング言語がある
  • クラス図で書くと以下のような関係になる。子クラスから親クラスに白抜きの矢印がむく。
image block
# 親クラス
class Animal:
    def __init__(self, name):
        self.name = name

    def say_name(self):
        print(self.name)


# 子クラス
class Cat(Animal):
    def __init__(self, name, age):
				# 子クラスで__init__を上書きしたため、親クラスの__init__を呼び出す処理を書いている
        super().__init__(name)  
        self.age = age

    def print_age(self):
        print('age', self.age)


# 子クラスのインスタンス生成
neko = Cat(name='NEKO', age=3)
# 親クラスのメソッドを呼び出す
neko.say_name()
# 子クラスのメソッドを呼び出す
neko.print_age()

▼ 実行結果

$ python sample_inheritance_1.py
NEKO
age 3
委譲(コンポジション)
  • クラスを継承するのではなく、クラスを内包することで別クラスのメソッドを利用する方法。
  • クラス内部で他クラスをインスタンス化することで利用できる。
    • インスタンス変数(self.~=他クラスのインスタンス)にインスタンスを持たせる
  • クラス図で書くと以下のような関係になる。呼び出し元から呼び出し先へ黒で塗り潰された依存の矢印を向ける。
image block
# 委譲先
class NamableObject:
    def __init__(self, name):
        self.name = name

    def say_name(self):
        print(self.name)


# 委譲元
class Cat:
    def __init__(self, name: str, age: int):
        self.named_object = NamableObject(name)
        self.age = age

    def say_name(self):
        self.named_object.say_name()

    def print_age(self):
        print("age", self.age)


if __name__ == "__main__":
    # 委譲元のインスタンス生成
    neko = Cat(name="NEKO", age=3)
    # 委譲先のメソッドを呼び出す
    neko.say_name()
    # 委譲元のメソッドを呼び出す
    neko.print_age()

▼ 実行結果

$ python sample_inheritance_2.py
NEKO
3
継承とコンポジションのどちらを使えばいいのか
is-a関係とhas-a関係から考える
  • 「AはBである」関係(is-a関係)のときは 継承
    • 例:「ニフティ」は「会社」である 。
  • 「AはBを持っている/含んでいる」関係(has-a関係)の時は コンポジション
    • 例:「自動車」は「エンジン」をもつ 。
とは言ったものの・・・
  • 継承は あまり使わない というトレンドになっている
  • 継承は関数の実体がどこにあるかが分かりにくくなる。
  • 親クラスの実装を意識する必要がある。(SOLID原則)