CodeGym /課程 /C# SELF /抽象類別與方法

抽象類別與方法

C# SELF
等級 22, 課堂 1
開放

1. 前言

抽象類別 就是你不能直接用 new 來 new 出來的類別。它的目的是讓你拿來當基底,讓其他更具體的類別去繼承。你可以把它想像成一個「模板」或「藍圖」,它定義了所有子類別都該有的結構和行為,但細節就交給子類別自己決定。

舉個例子比較好懂:想像你有個「交通工具」的概念。現實世界沒有人真的在「交通工具」上移動(除非你在腦內小劇場)。有人開車、有人坐船、有人搭飛機。抽象類別就是你的「交通工具」藍圖,它規定每個交通工具都要有移動的能力(Move),但怎麼移動就要子類別自己決定啦。


// 抽象類別範例
public abstract class Transport
{
    public string Model { get; set; }

    // 抽象方法 — 沒有實作
    public abstract void Move();
}

你如果想 new 一個 Transport 物件,編譯器會直接報錯!你可以試試看,編譯器會直接兇你:「不能建立抽象類別的實例」。

什麼時候要用抽象類別?

  • 想把不同但類似的物件的共通特徵和行為集中在一個地方。
  • 想要定義一個「統一規範」:比如「所有子類都要會移動」。
  • 有些方法可以寫共用邏輯,但有些細節要讓子類自己決定。

抽象類別超適合拿來設計大型系統的架構,當你有一堆共通「功能」但細節要讓每個子類自己搞定時,抽象類別就是你的好朋友。

2. 抽象類別 vs 一般類別

這裡常常會有疑問,所以我們來幫你整理一下!

抽象類別 vs 一般類別

  • 一般類別 可以直接用 new 建立,也可以有全部已經寫好的方法。
  • 抽象類別:
    • 不能直接 new(類別宣告要加 abstract)
    • 可以有已經寫好的方法,也可以有抽象(沒內容)的 method
    • 可以有欄位、屬性、建構子,甚至 static 方法

public abstract class Animal
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("動物在睡覺!");
    }

    public abstract void MakeSound();
}

3. 抽象方法與屬性

抽象方法

抽象方法就是在抽象類別裡宣告,但沒有內容(沒實作)。子類一定要自己實作這個方法。


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

    // 抽象方法
    public abstract void MakeSound();
}

抽象屬性

你也可以宣告抽象屬性,這樣所有子類都必須自己實作它:


public abstract class Shape
{
    // 抽象屬性
    public abstract double Area { get; }
}

4. 繼承抽象類別

抽象類別可以有抽象方法,也可以有已經寫好的方法。子類一定要實作所有抽象方法和屬性,不然它自己也會變成抽象類別。

我們來延伸一下之前「動物 app」的例子:


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

    public void Sleep()
    {
        Console.WriteLine($"{Name} 睡得很香!");
    }

    public abstract void MakeSound();
}

現在來寫一個一般(非抽象)類別:


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

你如果不實作 MakeSound(),編譯器就會報錯。

圖解:有抽象類別的類別繼承結構


+---------------------+
|   Animal (abstract) |
+---------------------+
| Name: string        |
| + Sleep()           |
| + MakeSound()*      |
+---------------------+
         ^
         |
  +------+-------+
  |              |
 Dog            Cat

* MakeSound() — 抽象方法

5. 多型實戰:抽象化怎麼用在實務上

我們來玩玩看這些東西在小 app 裡怎麼用。假設你有一個動物清單,裡面有狗跟貓。


List<Animal> animals = new List<Animal>
{
    new Dog { Name = "Sharik" },
    new Cat { Name = "Barsik" }
};

foreach (var animal in animals)
{
    animal.MakeSound(); // 多型呼叫:會跑自己那個
    animal.Sleep();     // 呼叫基底類別已經寫好的方法
}

Cat 類別也差不多這樣寫:


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

實務上的好處

抽象類別在大型 app 架構裡超常見。比如說,繪圖軟體通常會有個抽象類別 Shape,裡面有 Draw() 方法和 Area 屬性。各種圖形(像是矩形、圓形)就自己實作這些抽象方法。這樣你就可以一直加新圖形,不用重寫整個程式邏輯。

6. 抽象類別的特點與限制

不能 new 實例

編譯器絕對不讓你這樣搞:


Animal a = new Animal(); // 錯誤!

只有子類(具體類別)才能 new。

抽象類別可以包含:

  • 抽象方法宣告(沒實作)
  • 已實作的方法(有內容)
  • 屬性和欄位(抽象或已實作都行)
  • 建構子 — 沒錯,抽象類別可以有建構子,但只能被子類呼叫,不能直接 new。

抽象類別支援繼承

你可以設計一整串抽象類別的繼承結構:


public abstract class Creature
{
    public abstract void Live();
}

public abstract class Animal : Creature
{
    public abstract override void Live();
    public abstract void MakeSound();
}

如果繼承鏈上有類別沒實作所有抽象方法,它自己也要標成抽象類別!

7. 抽象類別與建構子

抽象類別可以有建構子,這些建構子會在子類的建構子裡被呼叫。
重點: 抽象類別的建構子通常會宣告成 protected,這樣就很明顯是給子類用的,不能直接 new。


public abstract class Animal
{
    public string Name { get; set; }
    
    // 建構子宣告成 protected
    protected Animal(string name)
    {
        Name = name;
        Console.WriteLine($"建立了一隻叫 {name} 的動物");
    }
    
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public Dog(string name) : base(name)
    {
        // base(name) 會呼叫 Animal 的建構子
        Console.WriteLine("建立了一隻狗");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} 說:汪!");
    }
}

為什麼要用 protected 建構子?

  • 意圖明確: protected 很明顯就是給子類用
  • 防止誤用: 雖然編譯器本來就不讓你 new,但 protected 建構子讓 API 更清楚,完全不會誤會
  • 好習慣: 遵守封裝和 API 清晰的原則

你也可以在抽象類別裡用 public 建構子,但 protected 更貼近它的用途。

8. 抽象方法 vs virtual 方法

抽象方法其實就是 virtual 方法的一種(雖然不用寫 virtual),但不能有內容。如果你想讓子類可以改寫(但不是一定要),就用 virtual。如果一定要,就只能用 abstract。


public abstract class Worker
{
    // 每個人都要實作
    public abstract void Work();

    // 可以改寫,也可以用預設
    public virtual void Rest()
    {
        Console.WriteLine("標準休息!");
    }
}
抽象方法 virtual 方法
基底類別有沒有內容 沒有,只有宣告(沒內容) 有,預設內容
可以在哪宣告 只能在抽象類別裡 任何沒被 sealed 的類別都可以,包括抽象類別
子類一定要改寫嗎 第一個非抽象子類一定要 不一定。可以改寫,也可以用預設
能不能直接呼叫 不能(沒內容) 可以(會跑預設內容)
主要用途 強制子類自己寫內容,定義「規範」 讓子類可以改行為(如果想改)

9. 抽象類別在你的專案裡怎麼用

我們繼續進化我們的學習 app。這次來做一個抽象類別,專門處理使用者輸入(比如不同型態的表單)。


public abstract class InputForm
{
    public string Title { get; set; }

    // 抽象方法,負責處理輸入流程
    public abstract void ShowAndHandleInput();
}

public class LoginForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("請輸入帳號:");
        string login = Console.ReadLine();
        Console.Write("請輸入密碼:");
        string password = Console.ReadLine();
        Console.WriteLine("驗證完成!");
    }
}

public class RegistrationForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("請設定帳號:");
        string login = Console.ReadLine();
        Console.Write("請設定密碼:");
        string password = Console.ReadLine();
        Console.WriteLine("註冊完成!");
    }
}

怎麼用:


InputForm[] forms = new InputForm[]
{
    new LoginForm { Title = "登入" },
    new RegistrationForm { Title = "註冊" }
};

foreach (var form in forms)
{
    form.ShowAndHandleInput();
}

這種寫法在你的程式越來越大時會越有感。抽象類別是打造可擴充系統的好基礎,DRY 跟 SOLID(不要重複自己、寫模組化、好擴充)就是你的好夥伴。

10. 用抽象類別和方法常見的錯誤

錯誤一:忘記實作抽象方法。
如果子類沒實作所有抽象方法和屬性,編譯器就會報錯。這很常發生,尤其是接手別人寫的 code 或是類別很多的時候。記得檢查有沒有漏掉什麼重要的。

錯誤二:想把方法同時標成 abstract 和 static。
這樣不行。抽象方法是給實例用的,static 根本不需要實例。這兩個概念互相衝突,編譯器直接不讓你過。

錯誤三:「偷懶」不想實作所有抽象成員。
你如果不想全部都實作,就只能把類別也標成 abstract。這不算錯,但可能代表你的架構有點亂。有時候可以把部分實作拉到中間的抽象類別,讓子類比較輕鬆。

2
任務
C# SELF, 等級 22, 課堂 1
上鎖
建立抽象類別與衍生類別
建立抽象類別與衍生類別
2
任務
C# SELF, 等級 22, 課堂 1
上鎖
抽象類別與多型
抽象類別與多型
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION