菜雞與物件導向 (5): 多型
多型算是比較三特性之中給人感覺比較溫和的了,不如說只要有了繼承,那麼多型的到來就是必然的。多型的定義是:不同的物件能夠做出一樣的行為,但必須由他們自己的程式碼來實作。
白話一點說就是:一樣的事,不同做法。
多型相對是比較好理解的,畢竟每個人做同一件事的方法本來就不太一樣。例如一樣是泡奶茶,英國就正常地泡,美國就會用微波爐;一樣是肉粽,有些人就是比較愛吃油飯;到了程式的世界裡也是,即使繼承了同一個物件,實現這個行為的方式也可以不同。
在此可以先推菜鳥工程師肉豬的這篇 Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) 中的說明。其中可以從例子看到儘管狗跟鳥都是繼承自動物這個類別,但對於「移動」這個方法,他們實作的方式並不一樣。這就是多型的範例。
我個人更喜歡用職位的方式去理解多型。
被繼承者就像是該職位的工作,例如說:Programmer
要會寫 C#、要會寫 SQL、要會 VB…等等,於是老闆就貼出了徵人啟示,要求新來的員工必須要有 Programmer
寫的能力:
public class Programmer
{
public virtual string WriteCSharp()
{
/* 努力地寫 C# */
}
public virtual string WriteSQL()
{
/* 努力地寫 SQL */
}
public virtual string WriteVB()
{
/* 努力地寫 VB */
}
}
補充說明:這邊用到的
virtual
和override
這兩個關鍵字
我們會在下一篇的覆寫進行介紹。有興趣的朋友也可以先偷看一眼呦!
於是今天小華就來面試了,不過他寫的程式碼品質…不怎麼樣。
public class Hua : Programmer
{
public override string WriteCSharp()
{
return "ShitCode";
}
public override string WriteSQL()
{
return "ShitCode";
}
public override string WriteVB()
{
return "ShitCode";
}
}
而小華應徵的隔天,小明也開開心心地來應徵了,他不只 C#、SQL 和 VB 都寫得很好,甚至還會泡英式奶茶:
public class Ming : Programmer
{
public override string WriteCSharp()
{
return "CleanCode";
}
public override string WriteSQL()
{
return "CleanCode";
}
public override string WriteVB()
{
return "CleanCode";
}
public Tea MakeTea()
{
return new Tea(teaName: "MilkTea");
}
}
火速通過面試之後,老闆就讓小明上工了:
public void Work()
{
Programmer programmer = new Ming();
programmer.WriteCSharp(); // "CleanCode"
programmer.WriteCSharp(); // "CleanCode"
programmer.WriteCSharp(); // "CleanCode"
}
過了幾天後,老闆決定讓小明和小華一起寫同個專案:
public void newProject()
{
Programmer programmer001 = new Ming();
Programmer programmer002 = new Hua();
programmer001.WriteCSharp(); // "CleanCode"
programmer002.WriteCSharp(); // "ShitCode"
programmer001.WriteCSharp(); // "CleanCode"
programmer002.WriteCSharp(); // "ShitCode"
}
這裡也就是多型的核心概念,用子類別實作出各式各樣不同的方法,藉此讓父類別的方法藉此達到延伸和多樣化的效果。
例如說一樣是 Programmer
的 WriteCSharp()
這個方法,小明的實現就是 return "CleanCode";
而小華的實作方式則是 return "ShitCode";
。
同樣地,最常被舉的例子就是動物。當有動物這個類別時,儘管狗跟貓都繼承了這個類別,但他們都可以對「叫聲」做出不同的實作。
因為有了多型,動物這個父類別,就能夠藉由子類別來完成擴展。更進一步來說,藉由子類別的擴展,我們能夠讓父類別做出各式各樣的事,而不需要更動父類別本身。
最常碰到的例子就是資料庫連線。例如說,我們可以讓 MySqlDBConnect
和 MongoDBConnect
都繼承 DBConnect
,但各自保有對應不同資料庫的實作。
如此一來,DBConnect
就獲得了用不同方法連線到不同資料庫的擴展,得到了對應狀況靈活使用的彈性。同時,使用 DBConnect
物件的其他物件也可以不用管現在的 DBConnect
連線是哪個子類別來工作的,只要知道能夠連線並取得資料就好,也達到了以封裝降低耦合的要求。
以上就是多型的核心。但當我們把子類別塞到父類別的殼裡面使用的時候,還需要注意:這時候的子類別已經是父類別的形狀了
當我們將小明宣告成工程師這一瞬間,小明已經不再只是小明,對老闆而言他就只是一個工程師,這裡只剩下「工程師」而不是「小明」,是 Programmer
而不是 Ming
。
在 Programmer programmer = new Ming()
執行完畢的同時,這裡就只剩下一個無情的寫程式機器,再也沒有小明。
當然,小明也不准在上班時間泡英式奶茶,我們不是請你來做這個的!
public void Work()
{
Programmer programmer = new Ming();
programmer.WriteCSharp();
programmer.MakeTea(); // Error: Programmer 未包含 MakeTea 的定義
}
這樣只會讓編譯器尷尬地說:請你照我們契約書上面走好嗎?畢竟這裡只剩下 Programmer
而不是 Ming
了,一個無情的寫程式機器是不需要,也不會知道怎麼泡英式奶茶的。
這邊我們就能知道:當子類別被以父類別的名義建立出來時,他就只能夠表現出父類別的樣子。換句話說,我們宣告的是什麼,他就只會做什麼,但要怎麼做倒是沒關係。雖然是一個令人悲傷的故事,但為了遵守封裝的精神,讓呼叫的物件不需要去了解是誰繼承、又由誰實作等等,為了物件界的秩序,這也是莫可奈何。
最後再說一聲,大話設計模式用兒子代替爸爸上台表演京劇的例子實在舉得很不錯,有興趣的可以去看看,這例子很能表現出那種披著父親的皮,用著自己的技術,但遮著臉不能被發現的感覺。如此傳神!
到這邊三大特性就說明完了,雖然我說明的相當模糊籠統,但希望概念能夠傳達到。
畢竟,就像我開頭引用的,我很喜歡這句「如果你問 100 個人這個問題,可能會得到 200 個答案,所以你一定要有自己獨到、有自信、精闢的見解或描述方式。」像我女友,當我問他物件導向的時候,她(大致上)是這樣說明的:
- 封裝:醬包跟麵的工作都在泡麵工廠做完了,我們只要拿來泡就好
- 繼承:我們可以買了泡麵之後,再自己加蛋加料
- 多型:一樣是泡麵,實作出來的口味都不一樣
說得我都要去買一碗來煮了。但是,物件是為了貼近我們的現實世界,而每個人的世界觀本來就不一樣。你必須自己體會,然後才會有自己的觀點。
至於那些說:你不是說要用卡牌當範例,怎麼突然多了個工程師小明小華?我只能說抱歉,洗澡的時候突然想到的,不寫不舒服。卡牌就想到再補吧,耶嘿。
下一篇開始就要進入抽象等等更模糊籠統的部分了,希望還能穩住哪。
本系列下一篇:菜雞與物件導向 (6): 抽象、覆寫
參考資料
- [心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 - 聊聊程式
- 物件導向(Object Oriented Programming)概念 - Po-Ching Liu - Medium
- Object Oriented物件導向-3:封裝(Encapsulation)、繼承(Inheritance)與多型(polymorphism)
- Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬
- 《大話設計模式》附錄:物件導向基礎
推薦系列文
同系列文章
- 菜雞與物件導向 (0): 前言
- 菜雞與物件導向 (1): 類別、物件
- 菜雞與物件導向 (2): 建構式、多載
- 菜雞與物件導向 (3): 封裝
- 菜雞與物件導向 (4): 繼承
- 菜雞與物件導向 (5): 多型
- 菜雞與物件導向 (6): 抽象、覆寫
- 菜雞與物件導向 (7): 介面
- 菜雞與物件導向 (8): 內聚、耦合
- 菜雞與物件導向 (9): SOLID
- 菜雞與物件導向 (10): 單一職責原則
- 菜雞與物件導向 (11): 開放封閉原則
- 菜雞與物件導向 (12): 里氏替換原則
- 菜雞與物件導向 (13): 介面隔離原則
- 菜雞與物件導向 (14): 依賴反轉原則
- 菜雞與物件導向 (15): 最少知識原則
- 菜雞與物件導向 (Ex1): 小結
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)