同じ呼び出しに対しオブジェクトごとに異なる動作をさせる仕組み。
ポリモーフィズム(多態性)とは、まったく同じメソッド呼び出しに対して、相手のオブジェクト(=モノの実体)の型ごとに異なる動作をさせる仕組みのことです。poly(多くの)+ morph(形)で「多くの形に化ける」という意味の言葉です。
身近な例で考えると、テレビのリモコンの電源ボタンに似ています。どのメーカーのテレビでも「電源ボタンを押す」という操作は同じですが、実際にどう電源が入るかは機種ごとに違います。押す側は中の仕組みを知らなくても、同じ操作で目的を果たせます。
上のツールで▶ボタンを押すと、同じ animal.speak() という呼び出しが、Dog なら「ワン」、Cat なら「ニャー」と、型に応じて違う結果になる流れを確認できます。
ポリモーフィズムを成り立たせているのがオーバーライド(override)です。これは、親クラスから受け継いだメソッドを、子クラスで同じ名前のまま中身だけ作り替えることを指します。
流れを整理すると次のとおりです。
・① 親が共通の操作を用意:Animal が speak() を宣言する
・② 子が上書き:Dog・Cat・Cow が speak() を自分用の内容に書き換える
・③ 動的束縛:実行時、変数が実際に指す型の speak() が自動的に選ばれる
よく混同されるオーバーロード(overload)とは別物です。オーバーロードは「同じクラス内で、同名だが引数の数や型が違うメソッドを並べる」ことで、こちらは型による動作の切り替えとは関係ありません。
ポリモーフィズムを使うと、呼び出す側を型ごとに場合分けしなくてよくなるのが最大の利点です。
ポリモーフィズムを使わないと、呼び出し側はこんなコードになりがちです。
ポリモーフィズムなら次の1行で済みます。
主な利点は次のとおりです。
・場合分け不要:if文で型を判定する必要がない
・拡張に強い:新しい型(Bird など)を足しても呼び出し側を直さなくてよい
・見通しが良い:呼び出し側のコードがシンプルに保たれる
ポリモーフィズムが特に便利なのは「新しい種類(型)を追加するとき」です。たとえば後から Bird(鳥)というクラスを追加したいとします。Bird が Animal を継承して speak() を「ピヨ」と上書きするだけで、呼び出し側のコード(animal.speak())は1文字も変えなくてよいのです。
なぜこれが便利なのかというと、呼び出し側と子クラスが「お互いの中身を知らなくてよい」状態になるからです。呼び出し側は「speak()があればOK」とだけ知っていれば済み、中が Dog なのか Cat なのか Bird なのかを気にしません。子クラスも「自分のspeakを実装する」だけです。
・なし:新しい型を足すたびにif文を追記 → 呼び出し側も毎回修正が必要
・あり:子クラスを追加するだけ → 呼び出し側は無変更のまま動く
身近な例で考えると、スマートフォンのアプリのようなものです。OSは「アプリを起動する」という操作だけ知っています。新しいアプリが増えても、OS側の「起動する」の仕組みを変える必要はなく、アプリ側が「起動されたらこう動く」を定義するだけで済みます。
多態性(ポリモーフィズム)は、OOPの3大要素のうち最後の1ピースです。3つを合わせると次のような役割分担になります。
・カプセル化:各オブジェクトのデータを安全に守る(土台の「安全」)
・継承:共通部分を親クラスにまとめて再利用する(コードの「再利用」)
・多態性:同じ呼び出しで型ごとに柔軟に動作させる(設計の「柔軟さ」)
3つの関係は次のように考えると整理しやすいです。カプセル化がオブジェクト1個を健全に保ち、継承でそのオブジェクトをひな型(親)として再利用でき、多態性で複数の種類を統一した操作で扱えるようになります。
身近な例で全体を振り返ると、テーマパークのアトラクションのようなものです。各アトラクション(オブジェクト)は内部の機械を見せない(カプセル化)、共通の「安全基準」を全アトラクションが引き継ぐ(継承)、そして案内係は「スタート」ボタンを押すだけでどのアトラクションも動き出す(多態性)。これが3要素の協力のイメージです。