菜雞與物件導向 (6): 抽象、覆寫
備註:這邊的抽象是指程式語言中的抽象類別,而非抽象化
抽象的概念很直接,請回想一下前面的例子就可以了:
當我們在用卡牌的例子時,雖然怪獸卡跟魔法卡都繼承了 Card 這個類別,但是我們仍然能
new Card()
來建立一張新卡牌,那…怪怪的吧,這張卡牌到底是什麼呀,空白的卡片嗎?
又或是動物的例子,我們的狗跟貓都繼承了哺乳類,那我們能實例化一個哺乳類嗎?我們的狗跟鳥都是動物,那我們能實例化一個動物嗎?
小明跟小華都繼承了工程師,那我們能 new 一個工程師嗎…?
有些類別就是這樣,它們負責定義共通的那些特性,然而它們本身不應該被實體化成一個物件,這種類別我們就應該把它們標記為抽象類別。
抽象類別在 C# 裡用 abstract
這個修飾詞來表示,可以加在類別或方法上。例如 abstract class Animal
就代表動物這個類別是個抽象類別,它不能被實例化。
而當加在方法上時,例如 public abstract void Eat()
就是代表這個進食的方法無法被叫用,只能由繼承者去重新定義這個方法。
abstract class Animal
{
public string color { get; set; }
public abstract void Eat();
}
那麼繼承者們,也就是衍生類別如何去重新定義父類別的方法呢?
所謂「欲戴王冠,必 Override」,這時候就必須使用覆寫(override
)。
覆寫是指對於像是前述的抽象方法時,在同名的方法前加上 override
關鍵字就可以讓程式知道你要覆寫這個方法(你不覆寫的話,編譯器還會生氣)。
例如前述的 Eat,狗就可以用 public override void Eat()
的方式去覆寫吃東西這個方法:
public class Dog : Animal
{
public string color { get; set; } = "Black";
public override void Eat()
{
/* 嚼嚼嚼 */
}
}
但有時候我們只是希望秉持著多型的精神,讓子類別有可以重新定義的彈性,這時候我們就會使用
虛擬(virtual
) 的方式去標記這個方法,如此一來就可以實作,同時也讓子類別可以覆寫。
例如可能狗有 public virtual void Eat()
這個進食的方法:
public class Dog
{
public virtual void Eat()
{
/* 嚼嚼熱狗 */
}
}
那假設我們有個 Giwawa
繼承了 Dog
,但牠也是吃熱狗的,就可以選擇不去覆寫 Eat()
:
public class Giwawa : Dog
{
/* 不打算實作 Eat,直接使用 Dog 類別的 Eat */
}
而當我們有了 RobotDog
這個類別,它就可以繼承並且重新改寫掉 Eat()
這個方法,從吃肉變成喝汽油。
public class RobotDog : Dog
{
public override void Eat()
{
/* 嚼嚼汽油 */
}
}
除了使用 override
去覆寫父類別的方法以外,也可以用 new
去隱藏父類別的方法:
public class CyberDog : Dog
{
public new void Eat()
{
/* 嚼嚼汽油 */
}
}
override
和 new
的差別在於多型時轉型成父類別時的行為:
override
會直接取代掉父類別的方法,即使轉型為父類別還是以子類別的實作為主new
則是會建立一個子類別專屬的方法,若轉型為父類別就會變回父類別的方法
我們直接用例子來看看吧,假設我們現在有「拉不拉多」和「機器狗」,都繼承了「狗」。差別在於拉不拉多 override 了 Eat()
這個方法,而機器狗 new 了 Eat()
這個方法:
public class Dog
{
public virtual void Eat()
=> Console.WriteLine("吃了熱狗");
}
public class Labrador : Dog
{
public override void Eat()
=> Console.WriteLine("吃了超大熱狗");
}
public class RobotDog : Dog
{
public new void Eat()
=> Console.WriteLine("喝了超多汽油");
}
接著讓我們來看看當他們被實例化之後,以及被轉型為父類別的時候的 Eat()
有什麼不一樣吧:
// Labrador => override
var lala = new Labrador();
lala.Eat(); // 吃了超大熱狗
((Dog)lala).Eat(); // 吃了超大熱狗
// ==============================
// RobotDog => new
var robot = new RobotDog();
robot.Eat(); // 喝了超多汽油
((Dog)robot).Eat(); // 吃了熱狗
可以看到使用 new
來覆寫的 RobotDog 在被轉型為 Dog 的時候突然就變回吃熱狗了!
要特別注意的是:當你覆寫了父類別的方法,卻忘記加上 override
的話,默認會當成是要 new
,所以覆寫的時候還是小心一點,具體地把 override
或 new
寫出來吧!
關於抽象和覆寫這部份的範例,因為我個人碰觸的比較少,唯恐我的舉例不夠深入,這邊再附上幾個不錯的範例,可以作為參考:
- C#雜記 — 介面(interface)、抽象(abstract)、虛擬(virtual)之我見 - Medium
- [C#] 利用 interface(介面) abstract(抽象) override(覆寫) inherit(繼承) 實作簡單範例 - 從入門到放棄
接著下一篇,我們就接著看這一部分的最後一片拼圖:介面吧!
本系列下一篇:菜雞與物件導向 (7): 介面
參考資料
- C#雜記 — 介面(interface)、抽象(abstract)、虛擬(virtual)之我見 - Medium
- [C#] 利用 interface(介面) abstract(抽象) override(覆寫) inherit(繼承) 實作簡單範例 - 從入門到放棄
- Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬
- Object Oriented物件導向-2:建構式(Constructor)、多載(Overloading)與覆寫(Overriding) - Sian
- Abstract - Microsoft Docs
- 了解使用 Override 和 New 關鍵字的時機 - Microsoft Docs
同系列文章
- 菜雞與物件導向 (0): 前言
- 菜雞與物件導向 (1): 類別、物件
- 菜雞與物件導向 (2): 建構式、多載
- 菜雞與物件導向 (3): 封裝
- 菜雞與物件導向 (4): 繼承
- 菜雞與物件導向 (5): 多型
- 菜雞與物件導向 (6): 抽象、覆寫
- 菜雞與物件導向 (7): 介面
- 菜雞與物件導向 (8): 內聚、耦合
- 菜雞與物件導向 (9): SOLID
- 菜雞與物件導向 (10): 單一職責原則
- 菜雞與物件導向 (11): 開放封閉原則
- 菜雞與物件導向 (12): 里氏替換原則
- 菜雞與物件導向 (13): 介面隔離原則
- 菜雞與物件導向 (14): 依賴反轉原則
- 菜雞與物件導向 (15): 最少知識原則
- 菜雞與物件導向 (Ex1): 小結
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)