本稿では、オブジェクト指向プログラミングにおけるポリモーフィズムとはどういうものなのかを解説する。
本稿で学べること
- ポリモーフィズムがどういう性質か?
- ポリモーフィズムにはどういうメリットがあるか?
- ポリモーフィックではないもの。
ポリモーフィズム
多態性や多相性として訳されるポリモーフィズムとは何なのか? その答えを知るために、まずは「ポリモーフィズム」という言葉の意味をひもといてみよう。polymorphismは、poly-morph-ismに分解できる。ploy-は「たくさんの」、-morph-は「かたち」、-ismは「考え方」といった意味だ。したがって、「たくさんのかたちの考え方」という意味になる。主語や述語を補えば「あるひとつの__モノゴト__には、__いくつか__の__カタチ__があるという考え方」という意味になる。
身近なポリモーフィズム: ひとつのWhatにいくつかのHow
ポリモーフィズムというと仰々しい専門用語に聞こえるが、その概念自体は意外と身近なものだったりする。日常を注意深く観察してみると、あちこちにポリモーフィックな性質があるモノゴトを見つけることができる。
例えば、あなたが旅行で、東京から福岡に行くことになったとする。ひとまずの目標は、ホテルのチェックイン時刻までに福岡に到着することだ。それさえ達成できれば、移動手段は何だってかまわないだろう。飛行機で移動するのが手っ取り早そうだが、鉄道が好きなら新幹線で行ったっていい。移動が旅行のひとつの醍醐味なら、ヒッチハイクも面白いかもしれない。どの移動手段も「東京から福岡に行く」というモノゴトとして言い片付けられるが、その中身はいろいろなカタチ(やり方)がある。こうしたひとつのWhatに対してHowがいくつかあるものは、総じてポリモーフィックな性質のものだ。
オブジェクト指向プログラミングでのポリモーフィズム
ポリモーフィズムは「あるひとつのモノゴトには、いくつかのカタチがある」性質だと述べたが、オブジェクト指向プログラミングの言葉を当てはめると、「あるひとつのメソッド名には、いくつかの実装がある」性質という説明になる。
例えば、「通知を送信する」というメソッド名があるとする。
class Notifier {
sendNotification(notification)
}
このsendNotification
というのがポリモーフィズムでは「あるひとつのメソッド名」に該当する。
これに対して「いくつかの実装」というのは、どうやって通知を送るかのその手段の実装になる。例えば、メール(SMTP)、SMS、プッシュ通知などが考えられる。それぞれをクラスに起こすとこんな感じになる:
class SMTPEmailNotifier {
sendNotification(notification) {
...
}
}
class SMSNotifier {
sendNotification(notification) {
...
}
}
class PushNotifier {
sendNotification(notification) {
...
}
}
ここで注目したいのが、それぞれはやっていること(実装)が全く異なるのに、sendNotification
メソッドは一言一句同じであるということだ。これが、「あるひとつのメソッド名には、いくつかの実装がある」というポリモーフィズムの性質を満たしたコードになる。
ポリモーフィズムのメリット
ここまではポリモーフィズムがどういう「性質」のものなのかについて触れてきただけで、ポリモーフィズムの性質を満たしたらどんなメリットがあるのかについては触れてこなかった。ここでは、ポリモーフィズムのメリットについて説明する。
その前に、上で取り上げた3つの通知手段をポリモーフィックではない実装にしたら、どんなデメリットがあるかを見てみよう。下のコードがポリモーフィックじゃない実装だ:
class SMTPEmailNotifier {
sendEmail(notification) {
...
}
}
class SMSNotifier {
sendSMS(notification) {
...
}
}
class PushNotifier {
push(notification) {
...
}
}
ポリモーフィックな実装との違いは、3つともメソッド名がバラバラなところだ。これでも、それぞれが通知を送れるので、書き方としては問題がない。しかし、もう少しこの実装を取り巻く状況に目をやると、そのデメリットが浮き彫りになる。
通知を受け取りたいユーザは、通知の手段を自分で選ぶことができるアプリを想像してみよう。ユーザは設定画面を開き、「メール通知」「SMS通知」「プッシュ通知」の中からひとつを選ぶ。その選択はユーザと紐付いた通知設定に保存されるとする。
このような場合、ユーザの通知設定を持ったオブジェクトを作り、そのオブジェクトから通知方法を取り寄せられるようにすることがあるだろう。
// ユーザの通知設定を扱うクラス
class UserNotificationSetting {
// ユーザの通知設定値。"email"か"sms"か"push"のどれかの値になる
var notificationType
// ユーザの通知設定に基づいて通知手段オブジェクトを返すメソッド
getNotifier() {
if (this.notificationType == "email") {
return new SMTPEmailNotifier()
} elseif (this.notificationType == "sms") {
return new SMSNotifier()
} elseif (this.notificationType == "push") {
return new PushNotifier()
}
}
}
UserNotificationSetting
(ユーザの通知設定)に基づいて、「新しいコメントがありました」という通知を送るメソッドがあったとしたら、どうなるか見てみよう。
sendNewCommentNotification(userNotificationSetting) {
notifier = userNotificationSetting.getNotifier()
if (notifier instanceof SMTPEmailNotifier) {
notifier.sendEmail("新しいコメントがありました")
} elseif (notifier instanceof SMSNotifier) {
notifier.sendSMS("新しいコメントがありました")
} elseif (notifier instanceof PushNotifier) {
notifier.push("新しいコメントがありました")
}
}
通知手段ごとにメソッド名が異なるため、通知手段ごとにif分岐を書く必要がある。ポリモーフィックでないため、こうした分岐が通知送信が必要になるいたるところで必要になってくるわけだ。
では、ポリモーフィックなメソッド名になっていたらどうだろうか?コードはとてもシンプルになる。どんな通知方法であっても、メソッド名はsendNotification
なので、if分岐が不要になる:
sendNewCommentNotification(userNotificationSetting) {
notifier = userNotificationSetting.getNotifier()
notifier.sendNotification("新しいコメントがありました")
}
ポリモーフィックでない実装の場合は、通知を送る箇所が増えるたびに、if分岐をコピペしていく必要があった。そして、通知手段がひとつ増えるたびに、コピペしたif分岐はすべて改修する必要も出てくる。
// いいね!がついたときに通知する機能
sendLikeNotification(userNotificationSetting) {
notifier = userNotificationSetting.getNotifier()
// 新規コメントがついたときに通知送信するコードをコピペし、通知内容を手直しした:
if (notifier instanceof SMTPEmailNotifier) {
notifier.sendEmail("あなたのコメントにいいねがつきました")
} elseif (notifier instanceof SMSNotifier) {
notifier.sendSMS("あなたのコメントにいいねがつきました")
} elseif (notifier instanceof PushNotifier) {
notifier.push("あなたのコメントにいいねがつきました")
} elseif (notifier instanceof DesktopNotification) {
// デスクトップ通知もサポートすることになったので、if分岐が追加された。
// 新規コメントがついたときに通知するコードのほうにもこの分岐を追加する必要がある。
notifier.sendDesktopNotification("あなたのコメントにいいねがつきました")
}
}
一方のポリモーフィックな実装では、変更が最小限に留められている:
sendNewCommentNotification(userNotificationSetting) {
notifier = userNotificationSetting.getNotifier()
notifier.sendNotification("新しいコメントがありました")
}
sendLikeNotification(userNotificationSetting) {
notifier = userNotificationSetting.getNotifier()
notifier.sendNotification("あなたのコメントにいいねがつきました")
}
以上の例でわかったように、「あるひとつのメソッド名には、いくつかの実装がある」というポリモーフィズムの性質に沿った設計には、コードをシンプルに保ちつつ、拡張しやすく、変更を最小限にしてくれるメリットがあるわけだ。
まとめ
- ポリモーフィズムとは、「あるひとつのモノゴトには、いくつかのカタチがある」という性質だった。砕いて言えば、ひとつのWhatにいくつかのHowがあることだった。オブジェクト指向プログラミングではとりわけ「あるひとつのメソッド名には、いくつかの実装がある」性質のことだった。
- ポリモーフィズムには、コードをシンプルに保ちつつ、拡張しやすく、変更を最小限にしてくれるメリットがあることを見た。
解説しなかったこと
今回はオブジェクト指向プログラミングにおけるポリモーフィズムの基本的な概念に触れただけで、以下の内容については解説できなかった。
- ポリモーフィズムはどういう言語仕様で実現されるか? 継承(Nominal Typing)、ダックタイピング、プロトタイプベース、Structural Typing。
- ポリモーフィズムの性質を応用したデザインパターンの紹介。Strategyパターンなど。
- 実は3つあるポリモーフィズムの種類: アドホック多相、パラメータ多相、部分型付け。
ポリモーフィズムについての基本的な解説記事(https://t.co/ihm4eV0qsd …)を書きました。
— suin❄️PHPでオブジェクト指向 (@suin) 2019年4月7日
続きを書くとしたら何がいいですか?