iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Modern Web

現在就學C# 與 ASP.NET Core系列 第 14 篇

Day 14|Polymorphism 與 Abstract:相同 Type,不同行為

  • 分享至 

  • xImage
  •  

Day 13 我們學到 Inheritance。

現在知道:

Animal animal = new Dog();

是成立的。

因為:

Dog is an Animal

所以:

Variable Type
→ Animal

實際 Object Type
→ Dog

但新的問題來了。

Dog
→ 汪

Cat
→ 喵

Dog 和 Cat 都是 Animal,但它們的行為不同。

如果 Variable Type 都是 Animal,程式怎麼知道該執行 Dog 還是 Cat 的行為?

這就是今天要解決的問題。

今天會學到:

  • virtual
  • override
  • Polymorphism(多型)
  • abstract
  • Abstract Class(抽象類別)

1. Dog 和 Cat 需要不同的行為

先建立:

class Animal
{
    public void MakeSound()
    {
        Console.WriteLine("Animal Sound");
    }
}

接著:

class Dog : Animal
{
}

class Cat : Animal
{
}

因為:

Dog is an Animal
Cat is an Animal

所以它們都有:

MakeSound()

但實際上:

Dog
→ 汪

Cat
→ 喵

也就是:

雖然 Dog 和 Cat 都是 Animal,但它們對同一個行為有不同的實作方式。

接下來要解決:

Derived Class 要怎麼提供自己的行為?


2. virtual 與 override

先修改 Animal:

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal Sound");
    }
}

這裡:

virtual

代表:

Base Class 提供預設實作,同時允許 Derived Class 改寫。

接著 Dog:

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪");
    }
}

Cat:

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("喵");
    }
}

這裡:

override

代表:

Derived Class 提供自己的版本。

可以先記:

virtual
→ Base Class 允許改寫

override
→ Derived Class 提供自己的實作

例如:

Dog dog = new Dog();

dog.MakeSound();

結果:

汪

到這裡我們知道:

Dog
→ 有自己的 MakeSound()

Cat
→ 有自己的 MakeSound()

但真正重要的問題是:

如果 Variable Type 是 Animal,還會執行 Dog 的 MakeSound() 嗎?


3. Variable 是 Animal,為什麼會執行 Dog 的行為?

假設:

Animal animal = new Dog();

這裡:

Variable Type
→ Animal

實際 Object Type
→ Dog

現在呼叫:

animal.MakeSound();

結果:

汪

而不是:

Animal Sound

為什麼?

因為 MakeSound() 使用了:

virtual
+
override

當程式執行時,會根據:

實際 Object Type

決定要執行哪個版本。

所以:

animal.MakeSound()
        ↓
實際 Object Type 是 Dog
        ↓
Dog.MakeSound()
        ↓
汪

這就是理解 Polymorphism 的關鍵。


4. 什麼是 Polymorphism?

再看:

Animal animal1 = new Dog();

Animal animal2 = new Cat();

兩個 Variable Type 都是:

Animal

但實際 Object Type 不同:

animal1
→ Dog

animal2
→ Cat

現在呼叫相同 Method:

animal1.MakeSound();
animal2.MakeSound();

結果:

汪
喵

可以整理成:

共同 Base Type
→ Animal

相同 Method
→ MakeSound()

不同 Object
→ Dog / Cat

不同實際行為
→ 汪 / 喵

這就是:

Polymorphism(多型)

先白話理解:

使用相同的 Base Type 呼叫相同 Method,不同 Object 可以執行自己的行為。

也就是:

相同 Base Type
        ↓
相同 Method
        ↓
不同 Object
        ↓
不同實際行為

Override 和 Polymorphism 的差別

這兩個概念有關,但解決不同問題。

override
→ Derived Class 怎麼提供自己的行為?

Polymorphism
→ 呼叫端怎麼透過共同 Type
   操作不同 Object?

例如:

Animal animal = new Dog();

animal.MakeSound();

可以拆成:

Dog.MakeSound()
→ override

Animal animal
→ 透過共同 Type 操作 Dog

執行 Dog.MakeSound()
→ Polymorphism

簡單理解:

Override 負責提供不同實作;Polymorphism 負責在執行時使用正確的實作。


5. Polymorphism 有什麼用?

如果呼叫端自己判斷每一種 Type,可能會寫:

if (animal is Dog)
{
    Console.WriteLine("汪");
}
else if (animal is Cat)
{
    Console.WriteLine("喵");
}

如果未來增加:

Cow
Bird
Lion

判斷可能越來越多:

if Dog
else if Cat
else if Cow
else if Bird
else if Lion
...

但使用 Polymorphism:

animal.MakeSound();

就可以了。

因為:

呼叫端
→ 只負責呼叫 MakeSound()

實際 Object
→ 決定 MakeSound() 怎麼執行

所以:

呼叫端只依賴共同的 Base Type,不需要一直判斷實際是哪一個 Derived Type。


放進 List<Animal> 更明顯

List<Animal> animals = new List<Animal>
{
    new Dog(),
    new Cat()
};

接著:

foreach (Animal animal in animals)
{
    animal.MakeSound();
}

結果:

汪
喵

foreach 裡完全不需要:

if (animal is Dog)

也不需要:

if (animal is Cat)

只有:

animal.MakeSound();

因為:

呼叫端
→ 決定要做什麼
→ MakeSound()

實際 Object
→ 決定怎麼做
→ 汪 / 喵

這就是 Polymorphism 的價值。


6. Base Class 沒有合理的預設行為怎麼辦?

前面使用:

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal Sound");
    }
}

這代表:

Animal
→ 已經提供 MakeSound() 的預設版本

Dog / Cat
→ 如果需要,可以 override

但仔細想:

Animal 本身真的有一個合理的預設叫聲嗎?

其實沒有。

因為:

Dog
→ 汪

Cat
→ 喵

Cow
→ 哞

所以真正想表達的不是:

Animal
→ 我先提供一個預設叫聲

而是:

所有 Animal
→ 都必須有 MakeSound()

但是

Animal
→ 不知道 MakeSound() 應該怎麼實作

Dog / Cat
→ 各自決定怎麼 MakeSound()

換句話說:

Base Class 想規定「一定要有這個行為」,但自己沒有合理的預設實作。

這種情況就適合使用:

abstract

7. abstract Method:規定一定要有,但 Base 不實作

把 Animal 改成:

abstract class Animal
{
    public abstract void MakeSound();
}

先看:

public abstract void MakeSound();

它只有 Method 宣告:

MakeSound()

沒有 Method Body。

也就是:

Animal 只規定「一定要有 MakeSound()」,但不規定要怎麼做。

因此:

Animal
→ 定義規則
→ 每一種 Animal 都必須有 MakeSound()

Dog
→ 決定 Dog 的 MakeSound() 怎麼做

Cat
→ 決定 Cat 的 MakeSound() 怎麼做

Dog:

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪");
    }
}

Cat:

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("喵");
    }
}

可以先記:

abstract
→ Base Class 提出要求
→ 自己不提供實作

override
→ Derived Class 回應要求
→ 提供自己的實作

如果 Derived Class 本身不是 Abstract Class,就必須實作繼承到的 Abstract Member。

例如下面這樣不行:

class Dog : Animal
{
}

因為 Animal 已經規定:

Dog
→ 必須提供 MakeSound()

所以必須:

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪");
    }
}

8. Abstract Class 是什麼?

剛才:

abstract class Animal
{
    public abstract void MakeSound();
}

這裡其實有兩個概念:

abstract class Animal
→ Animal 是 Abstract Class

public abstract void MakeSound()
→ MakeSound() 是 Abstract Method

兩者不要混在一起。


Abstract Method

代表:

這個 Method 沒有實作,Derived Class 必須提供實作。

例如:

public abstract void MakeSound();

Abstract Class

代表:

這個 Class 用來定義共同概念與規則,但不能直接建立 Object。

所以不能:

Animal animal = new Animal();

因為:

Animal
→ Abstract Class
→ 不能直接 new

但可以:

Animal animal = new Dog();

因為真正建立的是:

Dog Object

而 Animal 作為共同的 Base Type:

Variable Type
→ Animal

實際 Object Type
→ Dog

Abstract Class 不代表所有內容都要 abstract

Abstract Class 仍然可以包含:

Property
Constructor
一般 Method
virtual Method
abstract Method

例如:

abstract class Animal
{
    public string Name { get; private set; }

    public Animal(string name)
    {
        Name = name;
    }

    public void Eat()
    {
        Console.WriteLine("Eating");
    }

    public abstract void MakeSound();
}

這裡:

Name
→ 共同資料

Eat()
→ Animal 已經知道怎麼做
→ 提供共同實作

MakeSound()
→ Animal 不知道怎麼做
→ Derived Class 自己實作

所以:

Abstract Class

├── 可以放共同資料
├── 可以放已經實作好的共同 Method
└── 可以規定 Derived 必須實作的行為

9. virtual、abstract、override 差在哪裡?

這裡不用死背三個 Keyword。

先站在:

Base Class

的角度問:

Base Class 自己有沒有合理的實作?


情況一:Base 有合理的預設實作

例如:

public virtual void Move()
{
    Console.WriteLine("Animal is moving");
}

Animal 已經知道:

Move()
→ 預設可以怎麼做

所以可以直接提供實作。

Derived Class 可以直接沿用。

如果需要不同版本,再使用:

override

例如:

class Bird : Animal
{
    public override void Move()
    {
        Console.WriteLine("Bird is flying");
    }
}

所以:

virtual
→ Base 已經有預設版本
→ Derived 可以直接沿用
→ 也可以 override

可以把 virtual 想成:

「我有一個預設版本,你想改的話可以改。」


情況二:Base 沒有合理的預設實作

例如:

public abstract void MakeSound();

Animal 知道:

所有 Animal
→ 都應該 MakeSound()

但不知道:

MakeSound()
→ 到底應該怎麼做

所以 Base 不提供實作。

而是要求 Derived Class 自己完成。

abstract
→ Base 沒有預設版本
→ 只規定一定要有
→ Derived 必須提供實作

可以把 abstract 想成:

「我沒有預設版本,但你一定要自己完成。」


那 override 呢?

override 是 Derived Class 使用的。

它代表:

「我要提供自己的版本。」

例如:

public override void MakeSound()
{
    Console.WriteLine("汪");
}

所以三者角色不同:

virtual
→ Base Class
→ 有預設實作
→ Derived 可以改

abstract
→ Base Class
→ 沒有預設實作
→ Derived 必須實作

override
→ Derived Class
→ 提供自己的實作

最簡單的判斷方式

Base Class 有合理的預設實作嗎?
                │
        ┌───────┴───────┐
        │               │
       Yes              No
        │               │
    virtual          abstract
        │               │
        ↓               ↓
Derived 可選擇       Derived 必須
是否 override        提供實作

整理比較

比較 virtual abstract
誰宣告 Base Class Base Class
Base 有沒有實作 有 沒有
Derived 能不能直接沿用 可以 不行
Derived 是否一定要實作 不一定 是
Derived 如何修改 / 實作 override override
適合情況 Base 有合理預設行為 Base 沒有合理預設行為

一句話記住:

virtual
→ 我有預設版本
→ 你想改可以 override

abstract
→ 我沒有預設版本
→ 你一定要提供實作

override
→ 好,我提供自己的版本

真正要先問的是:

Base Class 到底知不知道這個行為應該怎麼實作?


10. 完整範例

最後把 Day 13 與 Day 14 組合起來。

Animal

abstract class Animal
{
    public string Name { get; private set; }

    public int Age { get; private set; }

    public Animal(
        string name,
        int age)
    {
        Name = name;
        Age = age;
    }

    public abstract void MakeSound();
}

Animal 負責:

共同資料
→ Name
→ Age

共同規則
→ MakeSound()

Dog

class Dog : Animal
{
    public int BarkVolume { get; private set; }

    public Dog(
        string name,
        int age,
        int barkVolume)
        : base(name, age)
    {
        BarkVolume = barkVolume;
    }

    public override void MakeSound()
    {
        Console.WriteLine("汪");
    }
}

Cat

class Cat : Animal
{
    public int PurrVolume { get; private set; }

    public Cat(
        string name,
        int age,
        int purrVolume)
        : base(name, age)
    {
        PurrVolume = purrVolume;
    }

    public override void MakeSound()
    {
        Console.WriteLine("喵");
    }
}

使用

List<Animal> animals = new List<Animal>
{
    new Dog(
        "Lucky",
        3,
        80
    ),

    new Cat(
        "Mimi",
        2,
        40
    )
};

接著:

foreach (Animal animal in animals)
{
    Console.WriteLine(animal.Name);

    animal.MakeSound();
}

結果:

Lucky
汪

Mimi
喵

核心流程:

List<Animal>
      ↓
foreach
      ↓
Animal animal
      ↓
animal.MakeSound()
      ↓
實際 Object Type 是誰?
   ├── Dog → 汪
   └── Cat → 喵

這就是:

Polymorphism


Day 14 小結

今天最重要的是:

virtual
→ Base 有預設實作
→ Derived 可以選擇是否改寫
abstract
→ Base 沒有預設實作
→ 規定 Derived 必須提供行為
override
→ Derived 提供自己的實作

而 Polymorphism:

共同 Base Type
        ↓
呼叫相同 Method
        ↓
實際 Object Type 不同
        ↓
執行不同實作

最重要的 Mental Model:

Animal animal = new Dog();

Variable Type
→ Animal

實際 Object Type
→ Dog

animal.MakeSound()
        ↓
Dog.MakeSound()
        ↓
汪

最後記住:

Override 是 Derived Class 提供自己的實作;Polymorphism 是透過共同的 Base Type 操作不同 Object,並執行實際 Object 對應的行為。


上一篇
Day 13|Inheritance:用繼承表達 Type Relationship
下一篇
Day 15|Interface 與 Composition:把需要的功能組合進來
系列文
現在就學C# 與 ASP.NET Core 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言