概要
Swiftの**「ポリモーフィズム」**についてまとめます。
以下のキーワードがモヤっとしている方におすすめです。
- Ad hoc polymorphism|アドホック多相
- Parametric polymorphism|パラメータ多相
- Subtyping|部分型付け
ポリモーフィズムとは
ポリモーフィズム(polymorphism / 多相)とは、**「複数の異なる型に対して、共通のインタフェースを提供すること」**です。簡単に言うと、同じ関数でも引数として渡す型によって異なる動作をさせることです。イメージとしては、動物は「鳴く」という共通のインタフェースを持っていますが、その動物によって鳴き声は異なる(型によって振る舞いが異なる)ようなものです。
ポリモーフィズムには3種類ある
ポリモーフィズムには以下の3種類があります。上から順に説明します。
- Ad hoc polymorphism(アドホック多相)
- Parametric polymorphism(パラメータ多相)
- Subtyping(部分型付け)
Ad hoc polymorphism(アドホック多相)
Ad hoc polymorphismは**「関数が引数の型によって異なる実装を持つ場合のポリモーフィズム」です。
Ad hoc polymorphismを実現するSwiftの言語機能には「関数のオーバーロード」**があります。
Ad hoc polymorphismの実装例
以下のような、PersonとBookの構造体を考えます。
struct Person {
let firstName: String
let lastName: String
}
struct Book {
let title: String
}
Person、Bookそれぞれの型に対する同一インタフェースの関数を多重に定義(オーバーロード)します。
func getName(of person: Person) -> String {
return person.firstName + " " + person.lastName
}
func getName(of book: Book) -> String {
return book.title
}
以下のように「複数の異なる型に対して、共通のインタフェースを提供する」メソッドができていることがわかります。
let person = Person(firstName: "Jhon", lastName: "Doe")
let book = Book(title: "Swift4 Programming")
print(getName(of: person))
print(getName(of: book))
Parametric polymorphism(パラメータ多相)
Parametric polymorphismは**「特定の型を指定しない場合のポリモーフィズム」です。
Parametric polymorphismを実現するSwiftの言語機能には「ジェネリクス(型パラメータ)」**があります。
Parametric polymorphismの実装例(ジェネリック関数)
以下のような、PersonとBookの構造体を考えます。
struct Person {
let firstName: String
let lastName: String
}
struct Book {
let title: String
}
ジェネリクスの型パラメータにより、総称型として引数を受け取ることで、Person、Bookどちらでも受け取れるようにします。
func getName<T>(of t: T) -> String {
if let person = t as? Person {
return person.firstName + " " + person.lastName
} else if let book = t as? Book {
return book.title
} else {
return ""
}
}
以下のように「複数の異なる型に対して、共通のインタフェースを提供する」メソッドができていることがわかります。
let person = Person(firstName: "Jhon", lastName: "Doe")
let book = Book(title: "Swift4 Programming")
print(getName(of: person))
print(getName(of: book))
Subtyping(部分型付け)
Subtypingは**「共通のインタフェースを持つ複数の型の異なる実装を実行する場合のポリモーフィズム」です。
Subtypingを実現するSwiftの言語機能には「スーパークラス(継承)」と「プロトコル」**があります。
Subtyping(スーパークラス)の実装例
nameというプロパティを持つ抽象インタフェースとしてNamedTypeをクラスで定義します。
class NamedType {
let name: String
init(name: String) {
self.name = name
}
}
PersonとBookはNamedTypeを継承する具象クラスとして定義します。
class Person: NamedType {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
super.init(name: firstName + " " + lastName)
}
}
class Book: NamedType {
let title: String
init(title: String) {
self.title = title
super.init(name: title)
}
}
関数の引数の型に抽象型であるNamedTypeを指定することで、NamedType自身やその継承クラスを引数として受け取れるようになります。
func getName(of namedType: NamedType) -> String {
return namedType.name
}
以下のようにスーパークラスによって「複数の異なる型に対して、共通のインタフェースを提供する」メソッドができていることがわかります。
let person = Person(firstName: "Jhon", lastName: "Doe")
let book = Book(title: "Swift4 Programming")
print(getName(of: person))
print(getName(of: book))
Subtyping(プロトコル)の実装例
nameというプロパティを持つ抽象インタフェースとしてNamedTypeをプロトコルで定義します。
protocol NamedType {
var name: String { get }
}
PersonとBookはNamedTypeに準拠する構造体(具象型)として定義します。
struct Person: NamedType {
let firstName: String
let lastName: String
var name: String {
return firstName + " " + lastName
}
}
struct Book: NamedType {
let title: String
var name: String {
return title
}
}
関数の引数の型に抽象型であるNamedTypeを指定することで、NamedTypeに準拠する型を引数として受け取れるようになります。
func getName(of namedType: NamedType) -> String {
return namedType.name
}
以下のようにプロトコルによって「複数の異なる型に対して、共通のインタフェースを提供する」メソッドができていることがわかります。
let person = Person(firstName: "Jhon", lastName: "Doe")
let book = Book(title: "Swift4 Programming")
print(getName(of: person))
print(getName(of: book))
まとめ
以上をまとめると、以下のようになります。
種類 | 和名 | 概要 | 実現機能 |
---|---|---|---|
Ad hoc polymorphism | アドホック多相 | hoge | 関数のオーバーロード(多重定義) |
Parametric polymorphism | パラメータ多相 | hoge | ジェネリクス |
Subtyping | 部分型付け | hoge | プロトコル、スーパークラス |