ポリモーフィズムのメリットって結局何なん?
オブジェクト指向の3大要素といえば「クラス(カプセル化)」「継承」「ポリモーフィズム」です。
「クラス(カプセル化)」と「継承」の意味やメリットは難なく理解できるのですが、「ポリモーフィズム」だけ意味や使い方はなんとなくわかるけど、深く理解できていないな...という感じでした。
最近「オブジェクト指向でなぜ作るのか」・「良いコード・悪いコードで学ぶ設計入門」という本を読んで、自分なりにポリモーフィズムのメリットが掴めた気がするので、「ポリモーフィズムのメリットって結局何なん?」って思っている初学者の助けになれればと思います!
繰り返し処理で見るポリモーフィズムのメリット
「オブジェクト指向でなぜ作るのか」と言う本では、ポリモーフィズムを以下のように紹介していました。
ポリモーフィズムはサブルーチンを呼び出す側のロジックを一本化する仕組み、すなわち「共通メインルーチン」作る仕組みである。
僕なりの理解に噛み砕くと、「クラスが共通で使える型を準備して、使い回せるようにする」という感じです。
そして、ポリモーフィズムのメリットを実感しやすいと思うのが、異なるクラス繰り返しの対象とする処理です。
ポリモーフィズムを使わない例とポリモーフィズム使った例を比較することで、直感的に分かると思います。
ポリモーフィズムを使わない例
class Dog {
void speak() {
System.out.println("ワンワン!");
}
}
class Cat {
void speak() {
System.out.println("ニャーニャー!");
}
}
public class Main {
public static void main(String[] args) {
List<Object> animals = List.of(new Dog(), new Cat());
for (Object obj : animals) {
if (obj instanceof Dog) {
((Dog) obj).speak();
} else if (obj instanceof Cat) {
((Cat) obj).speak();
}
}
}
}
繰り返し処理の対象であるanimalsが共通の型を持っていないため、for文の中でインスタンスの型をチェックして、キャストさせて、メソッドを呼び出すという面倒な処理が発生しています。
また、animalsに新しい動物を追加した場合には、for文の型チェックの条件も追加しなければならず、これがあまり良い設計ではないことは直感的に理解しやすいかと思います。
ひとことで言うと、ごちゃごちゃしてて分かりにくいコードですよね...
ポリモーフィズムを使った例
abstract class Animal {
abstract void speak();
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("ワンワン!");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("ニャーニャー!");
}
}
public class Main {
public static void main(String[] args) {
List<Animal> animals = List.of(new Dog(), new Cat());
for (Animal animal : animals) {
animal.speak();
}
}
}
繰り返し処理の対象であるanimalsが共通の型Animalをもったことで、for文の中がかなりスッキリしたことがわかります。
また、animalsに新しい動物を追加したい場合でも、for文の中は変更する必要がありません。
ポリモーフィズムを使わない例と比較すると、こちらの方が良い設計(拡張性・保守性が高い)であることがよく分かるかと思います。
ポリモーフィズムを使うだけで、ごちゃごちゃしてて分かりにくいコードが、スッキリしていてスマートなコードになりましたね!
おまけ
interfaceが定義されていることで、そのクラスにどんなメソッドが存在しているかがすぐに分かって、実装もしやすいことがチーム開発でとても助かるポイントでもあり、これがポリモーフィズムの一番のメリットだと思ってたんですが、「クラス(カプセル化)」「継承」と並んで3大要素と言えるほど大したものか?と思ってました...
これはポリモーフィズムのメリットというより、interfaceのメリットって感じみたいですね!
Discussion