假設今天要建立兩種動物:
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。
今天會學到:
is-a Relationship: 繼承語法base(...)
先看目前的設計:
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 自己的特性
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-aRelationship 是判斷 Inheritance 是否合理的重要方式。
先建立共同的 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
目前有:
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
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 建立時需要:
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 不會直接被 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
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。
前面說過:
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(多型)
假設還有:
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。
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 之後再正式介紹。
看到兩個 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 → 不要硬建立繼承
class Animal
{
public string Name { get; private set; }
public int Age { get; private set; }
public Animal(
string name,
int age)
{
Name = name;
Age = age;
}
}
class Dog : Animal
{
public int BarkVolume { get; private set; }
public Dog(
string name,
int age,
int barkVolume)
: base(name, age)
{
BarkVolume = barkVolume;
}
}
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 應該負責哪些資料。
今天最重要的觀念:
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-aRelationship。