ポリモーフィズムとはなにか?超わかりやすく解説します!

- ポリモーフィズムとはなに?
- どんなことができるようになるの?
- ポリモーフィズムのメリットは?

最近は「オブジェクト指向」というキーワードをよく聞くようになりました。
これは、いままでのプログラミング手法と異なり、
モノが自主的に作用しあう形でプログラムを組むというようなプログラミング手法
などと言われます。
「こんな難しく言われてもわかんねえよ!」って方。私もそうでした。
私はこの概念を理解するのに、一か月くらいかかりました。
そのくらい理解が難しいものであるということは確かです。
大まかに言って、オブジェクト指向を構成する要素は3つあります。
- 継承
- カプセル化
- ポリモーフィズム(今回解説)
これら3つはオブジェクト指向の基盤を担っているものです。
オブジェクト指向を理解するならば避けては通れない道でしょう。
今回は、その中でも一番理解が難しいとされる「ポリモーフィズム」について解説します。
全く知らない人は、本当に理解するのに時間がかかるかもしれません。
なので皆さんにより効率的に理解してほしいという想いから初めてプログラミングを学ぶ方にも優しい言葉で、できるだけわかりやすく解説していこうと思います。
オブジェクト指向の3つの特徴の1つのポリモーフィズムというのはどういうものなのでしょうか。
見ていきましょう!
ポリモーフィズム(polymorphism)
ポリモーフィズムとは、オブジェクトなどのデータ型に関する操作が統一的であることである。
なんて言われますが、正直こんな難しいこと言われてわかる人は少ないと思います。
もっともっと優しい言葉で解説しようと思います。
名前の由来
ポリモーフィズムとは、日本語で「多様性」「多態性」「多相性」と訳すことができます。
読み方は、ポリモーフィズムのほかに、ポリモルフィズムとも読めるそうです。[r]の発音の問題ですね。
単語の構造的には poly と morphism 分けられます。
poly(ポリ)には「多くの」という意味があります。
morphism(モーフィズム)は、簡単に「変化」といえばいいのかなと思います。
あるものからあるものへの「変化」を表しています。
以上より、ポリモーフィズム(polymorphism)とは、「多くの」「変化」と直訳することができますね。
「多くの変化が可能になる」という意味で「多様性」という言葉が当てはまるのだと思います。
poly(多くの)morphism(変化)→ 『多様性』
ポリモーフィズムとは、実は当たり前のこと?
実は、ポリモーフィズムとは、私たちの生活の中に「当たり前」に成り立っていることなのです。
まずは、難しく考えずにこう考えてください。
「モノ」が「そのモノ」らしく振舞うこと
まずは、簡単な例を出します。

このように犬と猫とアヒルがいます。
この3匹はそれぞれ、どのように鳴くでしょうか?

これは簡単ですよね。
そして、とても当たり前のように感じると思います。
それでは、ここからプログラミングの話に寄せていきます。
まずは、犬と猫とアヒルのクラスを作り、それぞれに鳴くという関数を作成します。
クラス 犬 { 鳴く() { "ワン" } };
クラス 猫 { 鳴く() { "ニャー" } };
クラス アヒル { 鳴く() { "クワッ" } };
それぞれの鳴き声を、こんな感じで表してみました。
そして、先ほどの「?は??のように鳴く」という風に表せば、
犬.鳴く();
猫.鳴く();
アヒル.鳴く();
このように書けるでしょう。
そして以下のように関数が実行されるはずです。
- 犬.鳴く() → “ワン”
- 猫.鳴く() → “ニャー”
- アヒル.鳴く() → “クワッ”
ここで考えるわけです。

このように考えれば、以下のような処理ができるはずです。
動物 = { 犬,猫,アヒル };
for (動物) { 動物.鳴く(); }
こんな感じでしょうか?
動物に犬、猫、アヒルが順に入って、鳴き声という関数を呼び出します。
こう書けば、犬は「ワン」、猫は「ニャー」、アヒルは「クワッ」と鳴くはずですね。
結果として先ほどの3行のコードと同じ処理を、きれいにまとめることができました。
ここで考えてもらいたいのが、どの動物に対しても鳴くという処理が同じ動作しかできないのであれば、この構文が使えないということです。
動物 = { 犬,猫,アヒル };
for (動物) { 動物.鳴く(); }
例えば、犬を入れても猫を入れてもアヒルを入れても、すべてにおいて「クワッ」と鳴いたらおかしいですよね?
逆に言えば、「中に入るものによって同じ関数でも違う処理を行える」というプログラミング言語自体の特徴があるからこそ、このような構文が使えるようになるということです。
先ほどの図を使えば、

最初の?によって鳴き声が変わるという特徴があるからこそ成立するのです。
そして、この特徴こそが「ポリモーフィズム」と呼ばれます。
もう一度言います。
「中に入るものによって同じ関数でも違う処理を行える」というプログラミング言語自体の特徴
これこそが「ポリモーフィズム」なのです。
オブジェクト指向プログラミング言語では、この特徴を当たり前のように持っています。
というより、「モノ」を基準として考える=オブジェクト指向言語では、ポリモーフィズムがなくては話にならないのです。
もうここまでくれば、なんとなく理解できるはずです。
はじめに書いた、
「モノ」が「そのモノ」らしく振舞うこと
ということを、オブジェクト指向プログラミングに寄せた表現にすれば、
呼び出した関数が呼び出し元のオブジェクトに適した振る舞いをすること
と言い換えることができます。
この、オブジェクト指向言語が当たり前に持つ特徴、仕組みこそが「難しい」といわれるポリモーフィズムの根本の意味なのです。
わざと難しい表現にすれば、「オブジェクトなどのデータ型に関する操作が統一的である」というような言い方になるわけです。
ポリモーフィズムのメリット
ざっくりと概要をお話ししたところで、「ポリモーフィズムを用いるとどのようなメリットがあるのか?」というところについて解説していきます。
ポリモーフィズムのないプログラミングに比べて以下の点で優れているといえます。
二つのメリットは根幹をみれば、どちらも似たりよったりな部分です。
それぞれ、ポリモーフィズムを利用しない場合と比較して、解説していきます。
コードがきれいにまとまる
ポリモーフィズムを利用すれば、とてもコードがきれいにまとまります。
これは実際にコードを見てもらったほうがわかりやすいので、プログラミング言語「C++」を使ってを紹介します。
まず下準備として、人間クラスを継承して、7か国の人をクラスとして作りました。
そして、各国のあいさつを表現した関数「Hello()」をそれぞれ持っていると考えてください。
以下で紹介する2タイプのコードは、どちらも7つの国のあいさつを呼び出す処理です。
それではまずは、ポリモーフィズム利用しない場合。
//それぞれでHello()関数を呼び出す
american.Hello();
japanese.Hello();
indian.Hello();
korean.Hello();
chinese.Hello();
russian.Hello();
german.Hello();
「ちょっとカッコ悪い!」
つぎに、ポリモーフィズム利用した場合。
//それぞれのオブジェクトのポインタを格納する配列を用意
Human* human[N] =
{ &american,&japanese,&indian,&korean,&chinese,&russian,&german };
//それぞれでHello()関数を呼び出す
for (int i = 0; i < N; i++) {
human[i]->Hello();
}
きれいにまとまった感じが見て取れるでしょうか。(ポインタとかはわからなくて大丈夫です。)
こんな風に、ポリモーフィズムを利用すれば、きれいにまとめることができるのです。
またこれは、変更に強くなる柔軟性も兼ね備えているのです。
変更に強くなる柔軟性
先ほどのコードを再利用します。
//それぞれのオブジェクトのポインタを格納する配列を用意
Human* human[N] =
{ &american,&japanese,&indian,&korean,&chinese,&russian,&german };
//それぞれでHello()関数を呼び出す
for (int i = 0; i < N; i++) {
human[i]->Hello();
}
ポリモーフィズムを利用しない場合はこれ↓です。
//それぞれでHello()関数を呼び出す
american.Hello();
japanese.Hello();
indian.Hello();
korean.Hello();
chinese.Hello();
russian.Hello();
german.Hello();
これは、以下のようなことを引き起こします。
- このような処理をもう一度したいとき、同じコードをもう一度書く必要がある。
- 単純にコードが縦長になる。
かっこ悪いし、こんな長々と書きたくないです。
またこのような処理がしたいとき、またこのコードを書かないといけなくなるというのが、よくない点でしょう。
例えば、もう一回この関数を呼び出すときに、
//それぞれでHello()関数を呼び出す
for (int i = 0; i < N; i++) {
human[i]->Hello();
}
こう書くのと、
//それぞれでHello()関数を呼び出す
american.Hello();
japanese.Hello();
indian.Hello();
korean.Hello();
chinese.Hello();
russian.Hello();
german.Hello();
どっちがいいですかという話です。
クラスが増えた分だけ、ポリモーフィズムを利用していない下のコードのほうが大変になります。
次にすべてのクラスに新たなメンバ関数Hoge()が増えた時を考えてみましょう。
単純に考えてください。以下の二つのコードどちらがいいと思いますか?
//それぞれでHello()関数,Hoge()関数を呼び出す
american.Hello();
american.Hoge();
japanese.Hello();
japanese.Hoge();
indian.Hello();
indian.Hoge();
korean.Hello();
korean.Hoge();
chinese.Hello();
chinese.Hoge();
russian.Hello();
russian.Hoge();
german.Hello();
german.Hoge();
//それぞれでHello(),Hoge()関数を呼び出す
for (int i = 0; i < N; i++) {
human[i]->Hello();
human[i]->Hoge();
}
圧倒的に下のほうが見やすいですし、簡単ですよね。
このように記述量が減ることで、修正が加えやすくなります。
大規模開発の際は、あとから変更する場面が多々あるので、修正がしやすいことに越したことはないでしょう。
もう一度になりますが、このような書き方ができるのは、呼び出した関数が呼び出し元のオブジェクトに適した振る舞いをしてくれるからです。
そしてこの当たり前の特徴こそがポリモーフィズムなのです。
最後に
理解や解釈が難しいポリモーフィズムですが、私も最初理解に苦しみましたので、いろんな記事を見るとか、直接触れてみて感じてみるとかするととてもいいと思います。
またメリットなどは個人的な見解も含んでいますので、実際にプログラミングしてみてここは違うんじゃないと思うこともあるかもしれません。一つ参考程度に見てください。
この記事はプログラミングを学び始める方向けに作っていますので、これからプログラミングを始める方に少しでも役に立てれば、参考にしてもらえればと思います。

以上、「ポリモーフィズムとはなにか?超わかりやすく解説します!」でした!
