OOPのポリモーフィズムを、オーバーライドからプロトコルまで一気に理解しようとして、混乱したことはありませんか?
オーバーロード、オーバーライド、プロトコルは似て見えるため、混同しやすいものです。
結論から言うと、ポリモーフィズムとは「同じ名前の呼び出しが、対象によって異なる動作をすること」です。オーバーライドは継承で、プロトコルは規約でこれを実現します。
この記事では、この3つの概念がどう関係するのかを順を追って整理します。
ポリモーフィズムとは?
ポリモーフィズム(Polymorphism)は、ギリシャ語で「複数(poly)の形(morph)」を意味します。
簡単に言えば、同じメッセージを送っても、受け取るオブジェクトごとに異なる反応をする性質です。
たとえば、犬と猫に同じように「声を出して」と言っても、犬はワンワン、猫はニャーと鳴きます。
呼び出し側は소리내()を呼ぶだけです。何を出力するかは各オブジェクトが決めます。
ポリモーフィズムの核心は「呼び出し側は知らなくてよい」という点です。
どんなものが来ても、同じコードで扱えるからです。
この性質によってコードは柔軟になります。新しい動物を追加しても、呼び出し側のコードを変更する必要はありません。
オーバーロードとオーバーライド、何が違う?
名前が似ているため、ここが最も混同しやすいポイントです。
オーバーロードとは、同じ名前の関数を、引数だけ変えて複数定義することです。
オーバーライドとは、親が定義した関数を子が再定義して上書きすることです。
言葉だけでは難しいので、コードで見てみましょう。
class Animal {
func sound() { print("...") }
}
class Dog: Animal {
override func sound() { print("ワンワン") } // オーバーライド
}
let a: Animal = Dog()
a.sound()
// 出力:ワンワン
ここで面白いのは、aの型がAnimalなのに、Dogの声が出る点です。
これが、実行時に実際のオブジェクトを見て決まるポリモーフィズムです。
オーバーロードは少し異なります。呼び出す関数はコンパイル時に決まります。
func add(_ a: Int, _ b: Int) -> Int { a + b }
func add(_ a: String, _ b: String) -> String { a + b }
print(add(1, 2)) // 出力: 3
print(add("か", "な")) // 出力:かな
そのため、オーバーロードを静的ポリモーフィズム、オーバーライドを動的ポリモーフィズムと呼ぶこともあります。
なぜプロトコルが必要なのか?
継承だけでもポリモーフィズムは実現できます。それなのに、なぜプロトコルがあるのでしょうか?
継承は「is-a」関係なので、親は1つでなければなりません。猫が動物でありながら、同時に別の階層に属するのは難しいでしょう。
プロトコルは「この規約を守れば何でもよい」という仕組みなので、はるかに自由です。
protocol Soundable {
func sound()
}
struct Cat: Soundable {
func sound() { print("ニャー") }
}
struct Car: Soundable {
func sound() { print("ブーブー") }
}
let things: [Soundable] = [Cat(), Car()]
things.forEach { $0.sound() }
// 出力:ニャー
// 出力:ブーブー
猫と車には継承関係がありません。それでも同じ配列に入れて、同じように扱えます。
継承という系譜がなくてもポリモーフィズムを得られること、それがプロトコルの力です。
Swiftのような言語が継承よりプロトコルを重視する理由でもあります。
継承とプロトコル、いつ使い分ける?
私が実務で判断するときの基準を表にまとめました。
| 状況 | 判断 |
|---|---|
| 共通コードを継承させたい | 継承(オーバーライド) |
| 関係はないが、同じ動作を保証したい | プロトコル |
| 複数の性質を同時に持たせたい | プロトコル(複数採用が可能) |
| 値型(struct)にポリモーフィズムを持たせたい | プロトコル |
簡単な原則をいくつか覚えておけば十分です。
- 親の実装を多く再利用するなら継承
- 「できる能力」を定義したいならプロトコル
- 拡張性が気になるなら、まずプロトコルを検討する
面接ではこう聞かれます
Q. オーバーロードとオーバーライドの違いを説明してください。
オーバーロードは同じ名前で異なる引数を定義するもので、コンパイル時に決まります。オーバーライドは継承したメソッドを子が再定義するもので、実行時に実際のオブジェクトを見て決まります。そのため前者を静的ポリモーフィズム、後者を動的ポリモーフィズムと呼びます。
Q. 継承の代わりにプロトコルを使うメリットは何ですか?
継承は親が1つに制限され、値型には使えません。一方、プロトコルは複数を同時に採用でき、structにも適用できます。関係のない型同士も同じ規約でまとめて扱えるため、拡張に向いています。
ポリモーフィズムは最初は抽象的ですが、「呼び出し側は知らなくてよい」という一文を押さえれば、ほかのことも自然に理解できます。
今日学んだオーバーライドとプロトコルを、ぜひ一度ずつコードで試してみてください。きっと身につきます。応援しています!

