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