iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Modern Web

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

Day 13|Inheritance:用繼承表達 Type Relationship

  • 分享至 

  • xImage
  •  

假設今天要建立兩種動物:

Dog
Cat

它們都有共同資料:

Name
Age

也有各自的特性:

Dog
→ BarkVolume

Cat
→ PurrVolume

看到共同資料時,很容易想到:

能不能把重複程式碼抽出來?

但學習 Inheritance 前,更重要的是先問:

Dog 是 Animal 的一種嗎?
→ Yes

Cat 是 Animal 的一種嗎?
→ Yes

這種關係稱為:

is-a Relationship

C# 可以透過:

Inheritance(繼承)

來表達這種 Type Relationship。

Inheritance 最重要的不是減少重複程式碼,而是表達合理的 Type Relationship。

今天會學到:

  • Inheritance
  • is-a Relationship
  • Base Class(基底類別)
  • Derived Class(衍生類別)
  • Base Type(基底型別)
  • Derived Type(衍生型別)
  • : 繼承語法
  • Constructor 與 base(...)
  • 什麼情況適合使用 Inheritance

1. 為什麼需要 Inheritance?

先看目前的設計:

class Dog
{
    public string Name { get; private set; }

    public int Age { get; private set; }

    public int BarkVolume { get; private set; }
}

class Cat
{
    public string Name { get; private set; }

    public int Age { get; private set; }

    public int PurrVolume { get; private set; }
}

可以看到:

Dog
├── Name
├── Age
└── BarkVolume

Cat
├── Name
├── Age
└── PurrVolume

Dog 與 Cat 都有:

Name
Age

但:

有共同內容,不代表一定要使用 Inheritance。

真正應該先判斷:

Dog is an Animal?
→ Yes

Cat is an Animal?
→ Yes

也就是:

Dog
→ 是 Animal 的一種

Cat
→ 是 Animal 的一種

既然 Dog 和 Cat 都屬於 Animal,就可以把共同資料整理到一個共同的 Class:

Animal

再讓 Dog 與 Cat 繼承 Animal。

Animal
→ 放共同資料

Dog
→ 保留 Dog 自己的特性

Cat
→ 保留 Cat 自己的特性

2. is-a Relationship

判斷是否適合使用 Inheritance,可以先問:

A 是 B 的一種嗎?

例如:

Dog is an Animal?
→ Yes

Cat is an Animal?
→ Yes

所以可以建立:

             Animal
                ↑
        ┌───────┴───────┐
        │               │
       Dog             Cat

可以理解成:

Animal
→ 比較共同的 Type

Dog
→ 更具體的一種 Animal

Cat
→ 更具體的一種 Animal

因此:

is-a Relationship 是判斷 Inheritance 是否合理的重要方式。


3. Base Class 與 Derived Class

先建立共同的 Animal:

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

    public int Age { get; private set; }
}

接著讓 Dog 繼承 Animal:

class Dog : Animal
{
}

C# 使用:

:

建立 Inheritance。

所以:

Animal
→ Base Class(基底類別)

Dog
→ Derived Class(衍生類別)

從 Type 的角度來看:

Animal
→ Base Type(基底型別)

Dog
→ Derived Type(衍生型別)

再加入:

class Cat : Animal
{
}

整體關係:

             Animal
                ↑
        ┌───────┴───────┐
        │               │
       Dog             Cat

可以先記住:

Base Class
→ 定義共同內容

Derived Class
→ 是 Base Class 的一種
→ 可以加入自己的差異

Inheritance 同時也能共用 Base Class 的成員。

但判斷是否應該使用 Inheritance 的重點仍然是:

is-a Relationship

4. 哪些資料應該放在哪裡?

目前有:

Name
Age
BarkVolume
PurrVolume

可以先用一個簡單原則判斷:

共同資料放在 Base Class,特定類型才需要的資料留在 Derived Class。

例如:

Animal
├── Name
└── Age

Dog
└── BarkVolume

Cat
└── PurrVolume

因為:

Name / Age
→ Animal 的共同資料

BarkVolume
→ Dog 特有

PurrVolume
→ Cat 特有

所以:

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

    public int Age { get; private set; }
}

Dog:

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

Cat:

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

簡單記:

共同資料
→ Base Class

特有資料
→ Derived Class

5. Constructor 與 base(...)

接著加入 Constructor:

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

    public int Age { get; private set; }

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

Name 與 Age 是 Animal 的資料,所以由:

Animal Constructor

負責初始化。


Dog 怎麼初始化 Animal 的資料?

Dog 建立時需要:

Name
Age
BarkVolume

其中:

Name / Age
→ Animal 負責

BarkVolume
→ Dog 負責

所以:

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

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

這裡:

base(name, age)

代表:

呼叫 Base Class 的 Constructor。

也就是把:

name
age

交給 Animal Constructor 初始化。

而:

BarkVolume = barkVolume;

由 Dog 自己處理。

流程:

new Dog("Lucky", 3, 80)
        ↓
Dog Constructor
        ↓
base("Lucky", 3)
        ↓
Animal Constructor
        ↓
初始化 Name / Age
        ↓
Dog 初始化 BarkVolume

可以簡單記:

Base Class 的資料
→ Base Constructor 負責

Derived Class 的資料
→ Derived Constructor 負責

Constructor 不會直接被繼承

要注意:

Constructor 不會直接被 Derived Class 繼承。

例如:

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

這是 Dog 自己的 Constructor。

而:

base(name, age)

負責呼叫 Animal Constructor。

可以記成:

Dog Constructor
      ↓
base(...)
      ↓
Animal Constructor

Cat 也是相同概念

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

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

責任分工:

Name / Age
→ Animal 初始化

PurrVolume
→ Cat 初始化

只是把同樣的規則套用到 Cat。


6. Derived Type 可以被視為 Base Type

前面說過:

Dog is an Animal

所以可以寫:

Animal animal =
    new Dog(
        "Lucky",
        3,
        80
    );

這行可以拆成兩部分:

Animal animal
→ Variable Type 是 Animal

new Dog(...)
→ 實際建立的是 Dog Object

也就是:

建立一個 Dog Object,但使用 Animal 型別的 Variable 來參考它。

實際 Object 仍然是 Dog,並沒有變成另一個 Animal。

Animal animal
      │
      │ 參考
      ↓
┌─────────────────┐
│   Dog Object    │
│                 │
│ Name            │
│ Age             │
│ BarkVolume      │
└─────────────────┘

之所以成立,是因為:

Dog is an Animal

也就是:

Derived Type(衍生型別)可以被視為 Base Type(基底型別)。


可以使用哪些成員?

如果:

Dog dog =
    new Dog(
        "Lucky",
        3,
        80
    );

可以使用:

dog.Name;
dog.Age;
dog.BarkVolume;

但如果:

Animal animal =
    new Dog(
        "Lucky",
        3,
        80
    );

可以直接使用:

animal.Name;
animal.Age;

但不能直接:

animal.BarkVolume;

因為 Variable Type 是:

Animal

而 Animal 沒有定義 BarkVolume。

目前先記住:

Variable Type
→ 決定目前可以直接使用哪些成員

實際 Object
→ 仍然是 Dog

這個概念下一篇會直接銜接:

Polymorphism(多型)


7. 有共同資料,不代表應該繼承

假設還有:

class AnimalReport
{
    public string Name { get; set; }

    public int Age { get; set; }
}

它和 Animal 都有:

Name
Age

那可以這樣嗎?

class AnimalReport : Animal
{
}

先問:

AnimalReport is an Animal?

也就是:

一份 AnimalReport,是不是一隻 Animal?

答案:

No

因為:

Animal
→ 動物

AnimalReport
→ 描述動物的資料

它們不是同一種 Type。

所以:

程式碼看起來很像
≠
Type 之間存在 Inheritance

不要只為了減少重複程式碼就使用 Inheritance。


8. is-a 與 has-a

再看另一種關係:

Dog has a Collar

Dog 有一個 Collar。

但是:

Dog is a Collar?
→ No

所以:

class Dog : Collar
{
}

並不合理。

可以先記:

Dog is an Animal
→ is-a
→ 可以考慮 Inheritance

Dog has a Collar
→ has-a
→ 不是 Inheritance

也就是:

is-a
→ Inheritance

has-a
→ 不是 Inheritance

has-a 與 Composition 之後再正式介紹。


9. 判斷 Inheritance 時問三個問題

看到兩個 Class 很像時,不要立刻使用 Inheritance。

先問:

① Dog 真的是 Animal 的一種嗎?
   → Yes

② Dog 可以合理地被當成 Animal 使用嗎?
   → Yes

③ 我是在表達「種類關係」,
   還是只是因為程式碼長得很像?

例如:

Dog is an Animal
✓

Cat is an Animal
✓

AnimalReport is an Animal
✗

可以整理成:

找到共同概念
      ↓
確認 Type Relationship
      ↓
Derived Type 是 Base Type 的一種嗎?
   ├── Yes → 可以考慮 Inheritance
   └── No  → 不要硬建立繼承

10. 完整範例

Animal

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

    public int Age { get; private set; }

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

Dog

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

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

Cat

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

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

使用

Dog dog =
    new Dog(
        "Lucky",
        3,
        80
    );

Cat cat =
    new Cat(
        "Mimi",
        2,
        40
    );

Console.WriteLine(dog.Name);
Console.WriteLine(dog.Age);
Console.WriteLine(dog.BarkVolume);

Console.WriteLine(cat.Name);
Console.WriteLine(cat.Age);
Console.WriteLine(cat.PurrVolume);

整體關係:

                 Animal
              Name / Age
                  ↑
         ┌────────┴────────┐
         │                 │
        Dog               Cat
         │                 │
    BarkVolume        PurrVolume

今日練習

建立:

class Vehicle
{
    public string Brand { get; private set; }

    public Vehicle(string brand)
    {
        Brand = brand;
    }
}

接著建立:

Car
Motorcycle

需求:

Car
├── Brand
└── DoorCount

Motorcycle
├── Brand
└── HasSidecar

先判斷:

Car is a Vehicle?
→ Yes

Motorcycle is a Vehicle?
→ Yes

再完成:

  • Car 繼承 Vehicle
  • Motorcycle 繼承 Vehicle
  • 使用 base(...) 初始化 Brand
  • DoorCount 留在 Car
  • HasSidecar 留在 Motorcycle

練習重點:

判斷 Type Relationship,以及每個 Class 應該負責哪些資料。


Day 13 小結

今天最重要的觀念:

Inheritance
→ 表達 is-a Relationship

Base Class
→ 定義共同內容

Derived Class
→ 是 Base Class 的一種
→ 可以加入自己的差異

共同資料
→ Base Class

特有資料
→ Derived Class

base(...)
→ 呼叫 Base Class Constructor

從 Type 的角度來看:

Base Type(基底型別)
→ 比較共同的 Type

Derived Type(衍生型別)
→ 是 Base Type 的一種
→ 可以被視為 Base Type

例如:

Animal animal = new Dog("Lucky", 3, 80);

代表:

Variable Type
→ Animal

實際 Object
→ Dog

最後記住:

程式碼重複,不代表一定要使用 Inheritance;真正要先確認的是 is-a Relationship。


上一篇
Day 12|Nullable Reference Types:理解 null 與安全存取
下一篇
Day 14|Polymorphism 與 Abstract:相同 Type,不同行為
系列文
現在就學C# 與 ASP.NET Core 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言