In programming languages and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.
在程式語言中,「多型」代表提供不同類型的實體一個統一的介面,或使用一個單一的符號來表示多個不同的類型。
Polymorphism is the ability of an object to take on many forms.
「多型」是一個物件可以擁有多種型態的方式
短短的解釋,但卻很難讓人理解他的真正意義。實際上「多型」本身有許多不同的實作方式,不如就我們就先來看看一些實際的例子吧
特設多型主要描述的是同一個方法需要處理多種 types 的時候,譬如在 JavaScript 當中,+
可以處理 number 和 string type 的資料,這裡的 +
表現的就是特設多型。
1 + 2 = 3
'1' + '2' = '12'
所以如果我們自己寫了一個方法,可以處理不同 types 的話,就是在展性特設多型的特性,像是下面的 cook
方法,可以傳入肉類或蔬菜類,最後產出一道菜
type Meat {
protein: xxx
}
type Vege {
nutrition: xxx
fiber: xxx
}
type Dish {
flavor: xxx
}
const cook = (
firstInput: Meat | Vege,
secondInput: Meat | Vege
): Dish => {
// ....
}
在剛剛的特設多型裡面,我們可以處理多個已知的 types,但如果遇到不知道 type 的狀況該怎麼辦呢?這時候我們可以把 type 當作是一個參數並動態做使用。
以下面的例子來看,有一個 getProperty
方法,當我們輸入 key 的時候,會回傳物件當中相對應的值。
function getProperty(obj: object, key: string) {
return obj[key]
}
不過我們希望限制輸入的 key 的 types,譬如
type Key = 'xxx' | 'yyy'
function getProperty(obj: object, key: Key) {
return obj[key]
}
然而每個物件的 keys 都不一樣,所以我們無法真正寫下 type Key
當中的內容,但是我們可以根據傳入的物件的 type 來動態指定 Key
的內容,像是
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
這裡的意思是,將 type Key
設定為傳入的物件當中的所有 keys。所以當我們傳入下面的 foo 物件之後,這時 type Key
就會是 'a' | 'b' | 'c' | 'd'
const foo = { a: 1, b: 2, c: 3, d: 4 }
getProperty(foo, "a") // 1 , Key = 'a' | 'b' | 'c' | 'd'
如果我們輸入了非指定的 key type,那麼就會出現錯誤,而不是回傳 undefined
const foo = { a: 1, b: 2, c: 3, d: 4 }
getProperty(foo, "z") // type error
在另外一種狀況下,我們會希望方法可以支援多種 types,不過我們還是得為這個方法的輸入設定 type。如果要想到所有可能的 type 可能沒有辦法,那麼,我們可以只設定 parent type (or super type),然後讓這個方法同樣可以接受所有的 child type (or subtype) 嗎?
許多程式語言有支援「子類型 Subtyping」這種多型的實作。以下面的例子來說,BaseballPlayer
type 完整包含了 Person
type,所以雖然 getName
方法定義輸入的 type 是 Person
,但我們仍然可以放入 BaseballPlayer
type 的物件,並順利得到結果
interface Person {
name: string;
}
interface BaseballPlayer {
name: string;
team: string;
}
const getName = (person: Person): string => {
retrun person.name
}
const ohtani: BaseballPlayer = {
name: 'Shohei Ohtani',
team: 'Angels'
}
getName(ohtani) // Shohei Ohtani
剛剛提到 BaseballPlayer
type 完整包含了 Person
type,其實也就等同於 BaseballPlayer
type 繼承了 Person
type
interface Person {
name: string;
}
interface BaseballPlayer extends Person {
team: string;
}
鴨子型別,這個名稱相當特殊的一種多型實現方式,它源自於 Duck Test:
"If it walks like a duck and it quacks like a duck, then it must be a duck"
意思就是不管前面這隻動物是屬於哪一類,只要他走路像鴨子、叫聲像鴨子,那麼我們就可以認定他是隻鴨子。
With normal typing, suitability is determined by an object's type. In duck typing, an object's suitability is determined by the presence of certain methods and properties, rather than the type of the object itself.
所以先前各種實現多型的情況下,我們會關注物件的型別,來決定該如何與物件互動。但在鴨子型別的狀況下,我們只關注物件的行為,只要他的行為如我們所預期,那麼就可以與他互動。
舉例來說,我們建立了一個 askSomeoneToRun
方法,專門負責叫人開始跑。不過這裡並沒有限定傳入的物件型別,而是只要傳入的物件本身有 run
方法,那麼就可以執行。也就是說,我們不關注物件的型別,只關注他本身的行為。
class BaseballPlayer {
constructor() {}
hit(): void {
console.log('hit like a baseball player')
}
run(): void {
console.log('run like a baseball player')
}
}
class FootballPlayer {
constructor() {}
shot(): void {
console.log('shot like a football player')
}
run(): void {
console.log('run like a football player')
}
}
const askSomeoneToRun = (someone: any) => {
someone.run()
}
const jeter = new BaseballPlayer()
const neymar = new FootballPlayer()
askSomeoneToRun(jeter) // run like a baseball player
askSomeoneToRun(neymar) // run like a football player
在物件導向的世界,我們用類別 (class) 和型別 (type) 來區別各種不同的物件,而許多時候我們的方法只會針對一種類別或型別來操作,但如果我們想要同時操作多種不同的類別和型別,那麼就需要用一些不同的方式來實現,這些不同的方式所實現的就是「多型」。
有了「多型」的概念和實現能力,我們可以讓程式碼本身具備更多的拓展性和重複使用性。