オーバーライドと多態性 | ポリモーフィズム | Python本格超入門

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

オーバーライドと多態性

オブジェクト指向プログラミング(OOP)の設計において、コードの再利用性を高め、拡張しやすい柔軟なシステムを作るための核となる機能が「継承」「オーバーライド」そして「多態性(ポリモーフィズム)」です。

これらを利用することで、プログラムの呼び出し側(メイン処理など)のコードを一切書き換えることなく、新しい機能や新しいキャラクター、新しいデータ処理パターンを後からいくらでも安全に追加できるようになります。本記事では、Pythonにおける具体的な実装例を交えてその仕組みを徹底解説します。

オーバーライドの基本

オーバーライド(Override:上書き)とは、親クラス(スーパークラス / 基底クラス)で定義されているメソッドと「全く同じ名前、同じ引数」のメソッドを、子クラス(サブクラス / 派生クラス)側で定義し直すことで、処理の中身を独自の挙動に塗り替える仕組みです。

これにより、子クラスは親クラスの基本的な共通機能を引き継ぎつつ、自分自身に特化した専門的な振る舞いを獲得できます。

# 親クラス(共通の枠組みを定義)
class Animal:
    def speak(self):
        return "鳴き声をあげます。"

# 子クラス(親のメソッドを自分の仕様で上書きする)
class Dog(Animal):
    def speak(self):
        return "ワンワンと吠えます。"

class Cat(Animal):
    def speak(self):
        return "ニャーニャーと鳴きます。"

# インスタンスの作成とメソッドの実行
dog = Dog()
cat = Cat()

print(dog.speak())  # 出力: ワンワンと吠えます。
print(cat.speak())  # 出力: ニャーニャーと鳴きます。

このコードでは、DogCat のインスタンスに対して speak() を呼び出した際、親クラスの処理ではなく、それぞれのクラスで上書きされた個別の処理が優先して実行されます。

ポリモーフィズム(多態性)の本質

オーバーライドされたメソッドが用意できると、オブジェクト指向最大のメリットであるポリモーフィズム(多態性・多様性)が実現します。

ポリモーフィズムとは、「相手の具体的なクラス型(犬なのか、猫なのか、あるいは将来追加される鳥なのか)を呼び出し側が意識することなく、共通のメソッド名を呼ぶだけで、それぞれのオブジェクトが適切な振る舞いを自動的に選択してくれる性質」を指します。

# 共通のインターフェース(操作窓口)として機能する関数
#  引数に渡されるのが何の動物クラスかをこの関数は知らないし、知る必要もない
def make_noise(animal_object):
    # 相手が誰であれ、ただ「speak()を呼び出す」という共通の操作を行うだけ
    print(animal_object.speak())

# 色々な子クラスのインスタンスをリストで用意
animals = [Dog(), Cat(), Dog()]

# ループで一斉に処理(ポリモーフィズムの実例)
for a in animals:
    make_noise(a)

上記の make_noise 関数の美しさは、引数の中身が Dog であれ Cat であれ、コードの記述が animal_object.speak() の1行で完結している点にあります。

もし将来、プログラムの仕様変更で新しく Cow(牛) クラスを追加することになっても、make_noise 関数側のコードは修正を1文字も加える必要がありません。新クラス側で speak() をオーバーライドしておくだけでシステムが自動的に拡張されます。これがポリモーフィズムが「変更に強い設計」と呼ばれる理由です。

親クラスの機能を再利用する(super()の活用)

メソッドをオーバーライドする際、親クラスの処理を完全に消し去るのではなく、「親クラスが元々持っている共通処理を実行した上で、子クラス特有の処理をちょっとだけ付け足したい」というケースが実務では非常によくあります。

その場合は、super() という組み込み関数を使うことで、子クラスの中から明示的に親クラスのメソッドを呼び出すことができます。

class Bird(Animal):
    def speak(self):
        #  super() を経由して、親クラス(Animal)の元の処理結果を取得する
        parent_behavior = super().speak()  # 「鳴き声をあげます。」が返る
        
        # 親の機能を利用しつつ、独自の文字列をドッキングして拡張する
        return parent_behavior + " しかし、鳥はチュンチュンと鳴きます。"

bird = Bird()
print(bird.speak())  # 出力: 鳴き声をあげます。しかし、鳥はチュンチュンと鳴きます。

実務においては、特に __init__ メソッド(初期化コンストラクタ)をオーバーライドする際に、親クラスの初期化ロジックを漏れなく実行させる目的で super().__init__() がほぼ必須の定型文として多用されます。

オーバーライドとポリモーフィズムの機能・役割まとめ

密接に関わり合う2つの概念の決定的な役割の違いです。

概念・キーワード プログラム上での具体的な役割・アプローチ 設計における最大のメリット
オーバーライド(具現化) 継承関係にある子クラスにおいて、親と同じ名前のメソッドの中身を書き換えて上書きする。 共通の処理フローを維持したまま、子クラス独自の専門的なビジネスロジックを美しく実装できる。
ポリモーフィズム(呼び出し) 異なるクラスのオブジェクトに対し、まったく同じメソッド名(操作インターフェース)で命令を送る。 呼び出し側のコードをシンプルに保ち、将来クラスが増減しても既存コードの書き換え(修正)が不要になる。
super()(接続) 子クラスのメソッド内から、上書きされる前の親クラスのメソッドを明示的に指定して実行する。 親クラスに書かれた共通コードの重複コピペを防ぎ、コードの一元管理(保守性の向上)に貢献する。

実務での注意点:ダック・タイピングという思想

JavaやC++といった厳密な言語では、明示的に親クラスを継承(extends)していないとポリモーフィズムは成立しません。しかし、Pythonは「ダック・タイピング(Duck Typing)」という柔軟な設計思想を採用しています。

"If it walks like a duck and quacks like a duck, it's a duck."
(もしそれがアヒルのように歩き、アヒルのように鳴くならば、それはアヒルである。)

つまりPythonでは、たとえ Animal クラスを継承していなくても、そのクラスに speak() というメソッドさえ実装されていれば、どんなオブジェクトであっても make_noise 関数に放り込んで正常にポリモーフィズムを動作させることができます。

自由度が高く拡張しやすい反面、「メソッド名のスペルミス(例: speack と書いてしまった)」があっても実行するまで文法エラー(AttributeError)にならず見落としやすいため、実務ではテストコードをしっかり書くか、型ヒント(Type Hints)や抽象基底クラス(abc モジュール)を活用してインターフェースの正確性を担保する工夫が求められます。

まとめ

  • 名前を揃えて中身を書き換える:オーバーライドは、親クラスが提示した「メソッド名」というルールを守ったまま、子クラスが独自の処理へと中身を上書き拡張する機能です。
  • 呼び出し側の世界を統一する:多態性(ポリモーフィズム)を活用すれば、メイン処理側はオブジェクトごとの細かい違いを意識せず、共通のメソッドをコールするだけの非常にクリーンでシンプルな設計を維持できます。
  • super() で車輪の再発明を防ぐ:上書きによって親の既存の共通処理を破壊してしまわないよう、super() を使って既存資産を賢く再利用・拡張するスマートな実装を心がけましょう。