不學 JAVA 換學 C# 之覺得心累 - L1:L1:ch13 繼承

AdSense

前言

在物件導向程式設計中,繼承 (Inheritance) 是一個非常核心的概念。透過繼承,我們可以讓一個類別 (稱作子類別衍生類別 Derived Class) 從另一個類別 (稱作父類別基底類別 Base Class) 延伸 (inherit) 出去,擁有基底類別的可繼承的欄位、屬性與方法,並且又可以有自己獨特的欄位、屬性與方法。這可以提高程式的重複使用性,也讓系統架構更有彈性與可擴充性。

語法

使用 : 繼承。僅支援單一繼承,也就是只能繼承一個類別。但是可以有繼承階層,例如:C 繼承自 B,而 B 繼承自 A,因此 C 可以使用 A 的欄位與方法。也可以基底類別被不同的衍生類別繼承,例如:CB 都繼承自 A

繼承階層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class 基底類別
{
// ...
}

class 衍生類別1 : 基底類別
{
// ...
}

class 衍生類別2 : 衍生類別1
{
// ...
}

範例:

  • HarryPotter1 類別透過 : Movie 繼承 Movie 類別。
  • HarryPotter1 可以使用 Movie 中的 Play() 方法。
  • HarryPotter2 繼承 HarryPotter1 類別。
  • HarryPotter2 可以使用 Movie 中的 Play() 方法和 HarryPotter1 中的 CastBasicSpell() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Movie
{
public void Play()
{
Console.WriteLine("播放電影...");
}
}

class HarryPotter1 : Movie
{
public void CastBasicSpell()
{
Console.WriteLine("施展基本魔法...");
}
}

class HarryPotter2 : HarryPotter1
{
public void CastTransfigurationSpell()
{
Console.WriteLine("施展變形咒...");
}
}

基底類別被不同的衍生類別繼承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class 基底類別
{
// ...
}

class 衍生類別1 : 基底類別
{
// ...
}

class 衍生類別2 : 基底類別
{
// ...
}

範例:

  • HarryPotter 可以使用 Movie 中的 Play() 方法,和自己的 CastBasicSpell() 方法。
  • SpiderMan 可以使用 Movie 中的 Play() 方法,和自己的 ShootWebs() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Movie
{
public void Play()
{
Console.WriteLine("播放電影...");
}
}

class HarryPotter : Movie
{
public void CastBasicSpell()
{
Console.WriteLine("施展基本魔法...");
}
}

class SpiderMan : Movie
{
public void ShootWebs()
{
Console.WriteLine("射出蜘蛛網...");
}
}

Protected 欄位與方法

之前有提過封裝與存取修飾詞,而修飾詞 protected 表示只有同一個類別內或其衍生類別可以存取,資料在基底類別與衍生類別之間共享。protected 介於 privatepublic 之間,提升封裝性又保留靈活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Movie
{
protected string _title;
private string _privateNote;

public Movie()
{
this._title = "電影名稱";
this._privateNote = "這是基底類別";
}
}

class HarryPotter : Movie
{
public HarryPotter()
{
// 因為 title 是 protected,所以可以在 HarryPotter 衍生類別裡面取得
this._title = "哈利波特";
// 不能這樣寫,因為 _privateNote 是 private,不能在衍生類別裡面取得
// this._privateNote = "這是衍生類別";
}

public void DisplayTitle()
{
// 因為 title 是 protected,所以可以在 HarryPotter 衍生類別裡面取得
Console.WriteLine($"Title: {_title}");
}
}

image

關鍵字 base

base 表示基底類別本身,用來呼叫基底類別 (base class) 中的建構子、欄位、屬性或方法

如果基底類別的建構式有參數 (像是:line 5 - line 8),則衍生類別需要明確標示建構式的繼承,例:line 18。

在衍生類別中想要呼叫基底類別的方法,例:line 24。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Movie
{
protected string _title;

public Movie(string title)
{
this._title = title;
}

public void ShowInfo()
{
Console.WriteLine("電影資訊");
}
}

class HarryPotter : Movie
{
public HarryPotter(string title) : base(title) // 呼叫基底類別建構子
{
}

public void ShowMagic()
{
base.ShowInfo(); // 呼叫基底類別方法
Console.WriteLine("霍格華茲魔法學校");
}
}

// 程式進入點
class Program
{
public static void Main(string[] args)
{
HarryPotter m = new HarryPotter("哈利波特1");
m.ShowMagic();
}
}

// 顯示結果:
// 電影資訊
// 霍格華茲魔法學校

如果基底類別的建構式沒有參數 (像是:line 5 - line 8),則衍生類別不需要明確標示建構式的繼承,例:line 13 - line 16。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Movie
{
protected string _title;

public Movie()
{
this._title = "電影名稱";
}
}

class HarryPotter : Movie
{
public HarryPotter()
{
this._title = "哈利波特";
}

// 上面 line 13 - line 16 等同如下
// public HarryPotter() : base()
// {
// this._title = "哈利波特";
// }
}

利用 virtual + override 覆寫基底類別的方法

若基底類別的方法使用 virtual 修飾詞,衍生類別就可以用 override 進行覆寫、修改邏輯,而不同的衍生類別就可以有自己客製化的邏輯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Movie
{
protected string _title;

public Movie(string title)
{
this._title = title;
}

// 使用 virtual 加在回傳型態前,表示此 method 可以被任何衍生類別覆寫 override
public virtual void ShowGenre()
{
Console.WriteLine($"{_title} 是一般電影類別");
}
}

class HarryPotter : Movie
{
public HarryPotter(string title) : base(title) // 呼叫基底類別建構子
{
}

// 使用 override 加在回傳型態前,表示此 method 覆寫基底類別的 method
public override void ShowGenre()
{
Console.WriteLine($"{_title} 是奇幻冒險");
}
}

// 程式進入點
class Program
{
public static void Main(string[] args)
{
HarryPotter m = new HarryPotter("哈利波特1");
m.ShowGenre();
}
}

// 顯示結果:
// 哈利波特1 是奇幻冒險

抽象類別 (Abstract Class) 與抽象方法 (Abstract Method)

當基底類別提供設計架構而不提供實作時,可以使用 abstract 類別與方法。目的:強迫衍生類別一定要定義該方法的內容。

  • abstract class 無法被實體化,只能被繼承。
  • abstract method 沒有定義內容,一定要放在 abstract class 中,且必須在子類別中用 override 覆寫並實作內容。
1
2
3
4
5
6
7
8
9
10
11
12
abstract class Movie // 抽象類別
{
public abstract void PlayTheme(); // 抽象方法,沒有實作內容
}

class HarryPotter : Movie
{
public override void PlayTheme()
{
Console.WriteLine("Playing Hedwig's Theme...");
}
}