UnsplashのXiaole Taoが撮影した写真
ポリモーフィズムは、オブジェクト指向プログラミングにおける中核的な概念のひとつです。「理解しているつもり」でも、設計や実装の中で適切に使えていないケースは少なくありません。ここでは、ポリモーフィズムの定義・種類・使いどころをまとめ、なぜこの概念が保守性や拡張性に関わるのかを説明します。
ポリモーフィズム(polymorphism)とは、同じ操作(メソッド呼び出し)であっても、オブジェクトの実体に応じて異なる振る舞いをさせられる性質を指します。オブジェクト指向プログラミングでは、抽象化と組み合わせることで、変更に強い設計につながります。
オブジェクト指向プログラミングは、主に次の3要素から構成されます。
この中でポリモーフィズムは、「共通の扱い方で実装を差し替えられる」点に意味があります。条件分岐に頼りすぎない設計にしやすくなり、その結果、コードの見通しや拡張性を保ちやすくなります。
polymorphism は、ギリシャ語の「poly(多くの)」と「morph(形)」に由来します。プログラミングにおいては、単一のインターフェースに対して複数の実装が存在する状態を意味します。
ポリモーフィズムは、「同じ呼び出しで、異なる処理が実行される仕組み」と要約できます。呼び出し側は実装の違いを意識せず、共通の型として扱える点が本質です。
特に業務システムや長期運用を前提とした開発では、仕様変更に対して変更点を局所化できるかが重要です。ポリモーフィズムは、その設計を支える考え方のひとつです。
同名のメソッドを、引数の型や数の違いで使い分ける手法です。これはコンパイル時に解決される仕組みであり、実行時多態とは区別されます。
型をパラメータとして抽象化し、特定の型に依存しない処理を記述する方法です。型安全性と再利用性を両立できる点が特徴です。
継承やインターフェースを利用し、上位型として扱ったオブジェクトに対して、実行時に適切な実装が選ばれるポリモーフィズムです。一般に「ポリモーフィズム」と言う場合、この形態を指すことが多くあります。オーバーライドは、その代表的な実現手段のひとつです。
明示的な型関係ではなく、メソッドの存在によって振る舞いを成立させる考え方です。主に動的型付け言語で用いられます。
親クラスの型でオブジェクトを扱い、処理の違いはサブクラスに委ねます。これにより、呼び出し側のコードを変更せずに振る舞いを追加できます。
実装ではなく契約(インターフェース)に依存する形にすると、差し替えやテストがしやすくなります。ただし、継承とインターフェースのどちらが適切かは、共通実装を共有したいのか、振る舞いだけを規定したいのかで変わります。
abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal[] animals = { new Dog(), new Cat() };
for (Animal animal : animals) {
animal.makeSound();
}
}
}呼び出し側は Animal 型として同じ makeSound() を呼んでいるだけですが、実行時には実体に応じて Dog と Cat の処理が選ばれます。これがポリモーフィズムの典型例です。
ポリモーフィズムは、処理の流れは同じまま対象だけが増えていく場面で特に効果を発揮します。たとえば、支払い方法ごとの決済処理、通知手段ごとの送信処理、ファイル形式ごとの出力処理などです。呼び出し側に条件分岐を増やさず、新しい実装を追加しやすくしたいときに向いています。
ポリモーフィズムを実現するための手段のひとつであり、目的そのものではありません。
内部実装を隠蔽することで、呼び出し側は実装の違いを意識せずに扱えるようになります。
実行時に呼び出すメソッドを決定する仕組みで、実行時多態の土台になります。
ポリモーフィズムは、単なる文法機能としてではなく、設計の考え方として捉える必要があります。共通の操作で多様な振る舞いを実現できるため、変更に強く、読みやすく、拡張しやすいコードにつながります。オブジェクト指向を扱うなら、きちんと理解しておきたい概念のひとつです。
同じ操作で異なる振る舞いを実行できるオブジェクト指向の性質です。
変更に強い設計につながり、保守性と拡張性を高めやすくなるためです。
広義には含まれますが、一般に説明される実行時多態とは区別されます。
多くの場面では、サブタイプポリモーフィズム(オーバーライド)を指して説明されます。
一概には言えません。振る舞いの契約だけを示したいならインターフェース、共通実装や状態を共有したいなら抽象クラスや継承が向くことがあります。
呼び出し側の条件分岐を増やさずに、実装側で振る舞いを切り替えられる点です。
文法機能として理解して終わりになり、設計意図(差し替えや変更点の局所化)を見落としやすい点です。
言語特性によるため、必須ではありません。
通常は無視できる範囲であることが多く、設計上の利点が上回るケースが一般的です。
将来の拡張や差し替えが想定される処理で有効です。