1. 前言
我們來用鳥瞰的角度看抽象:想像一個大辦公室,不同員工做著不同的事。他們可能有共同的職稱 — "員工",但每個人有自己的專屬任務。但如果你是老闆,你只在乎每個員工都能 "執行工作",至於怎麼做你根本不用管:程式設計師寫 code,會計師打報表。這種「大家都有共同合約,細節交給專家」的想法,就是正確建構類別階層的核心。
那在 code 裡長怎樣?
一切從基礎的抽象類別開始。它描述了所有子類都必須會的東西。
public abstract class Employee
{
public string Name { get; }
public Employee(string name)
{
Name = name;
}
// 抽象方法 — 所有員工的合約
public abstract void DoWork();
}
接下來是衍生類別(子類)— 程式設計師和會計師:
public class Programmer : Employee
{
public Programmer(string name) : base(name) { }
public override void DoWork()
{
Console.WriteLine($"{Name} 寫 code");
}
}
public class Accountant : Employee
{
public Accountant(string name) : base(name) { }
public override void DoWork()
{
Console.WriteLine($"{Name} 算錢");
}
}
現在注意:我們可以建立一個員工清單 — 不管是會計還是工程師,甚至你想加什麼都可以(抽象完全沒意見!)。
Employee[] office = new Employee[]
{
new Programmer("瓦西亞"),
new Accountant("塔妮亞")
};
foreach (Employee emp in office)
{
emp.DoWork(); // 每個人做自己的事
}
瓦西亞 寫 code
塔妮亞 算錢
你看,現在一切變得多麼漂亮、通用,最重要的是超級可擴展。你可以加一個新類別,比如 Manager,現有的 code 完全不用改!
2. 怎麼用抽象建構階層
幾乎每本 OOP 教科書都會用動物當例子,我們也來跟風一下。
抽象類別 — 當「大獨裁者」
public abstract class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// 所有動物都要會發出聲音
public abstract void MakeSound();
// 但不是每個都會飛,所以:
public virtual void Fly()
{
Console.WriteLine("我不會飛。");
}
}
子類:一定要會發出聲音,也可以覆寫其他方法
public class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 喵!");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 汪!");
}
}
public class Eagle : Animal
{
public Eagle(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 嘶叫!");
}
public override void Fly()
{
Console.WriteLine($"{Name} 在天空飛翔!");
}
}
現在來組一個不同動物的集合:
Animal[] zoo = new Animal[]
{
new Cat("巴爾西克"),
new Dog("沙里克"),
new Eagle("奧列爾")
};
foreach (Animal animal in zoo)
{
animal.MakeSound();
animal.Fly();
}
巴爾西克: 喵!
我不會飛。
沙里克: 汪!
我不會飛。
奧列爾: 嘶叫!
奧列爾 在天空飛翔!
你看,現在用任何子類都超簡單:我們根本不用管集合裡是哪種物件。抽象負責「合約」,細節交給具體實現。
3. 多層級的抽象
抽象不只是分成基礎類別和直接子類。在複雜系統裡,常常會有多層級抽象,一個抽象類別可以基於另一個抽象類別。這就像千層蛋糕(或史瑞克說的洋蔥):每一層都藏掉不必要的細節,只留需要的東西。
例子:交通工具階層
. Vehicle (abstract)
/ | \
Car Airplane Boat
ElectricCar JetAirplane SailBoat
這裡 Vehicle 設定了通用原則(比如 Move() 方法),但它不知道車子或飛機怎麼動。這些由子類決定。更具體的類別像 ElectricCar 或 JetAirplane,可以加自己的細節,擴充功能。
階層方塊圖:
. +------------------+
| Vehicle | <--- 抽象類別
+------------------+
/ \
+-------+ +-------+
| Car | | Boat | <--- 中間抽象或具體類別
+-------+ +-------+
Code:基礎抽象類別
public abstract class Vehicle
{
public string Model { get; }
public Vehicle(string model)
{
Model = model;
}
// 抽象方法
public abstract void Move();
}
中間抽象類別
有時中間層也需要自己的抽象!
public abstract class Car : Vehicle
{
public Car(string model) : base(model) { }
public override void Move()
{
Console.WriteLine($"{Model} 在路上行駛。");
}
// 抽象方法 — 不是每台車都電動:
public abstract void RefuelOrCharge();
}
具體實現
public class ElectricCar : Car
{
public ElectricCar(string model) : base(model) { }
public override void RefuelOrCharge()
{
Console.WriteLine($"{Model} 用電充電。");
}
}
public class GasolineCar : Car
{
public GasolineCar(string model) : base(model) { }
public override void RefuelOrCharge()
{
Console.WriteLine($"{Model} 加汽油。");
}
}
結果:多層級抽象讓你更容易加新類別和功能。
視覺化:類別階層範例
| 類別 | 型態 | 抽象 | 父類 | 特殊行為 |
|---|---|---|---|---|
| Vehicle | 基礎 | 是 | - | 抽象方法 Move() |
| Car | 中間 | 是 | Vehicle | 抽象 RefuelOrCharge() |
| ElectricCar | 最終 | 否 | Car | 實作 RefuelOrCharge() |
| GasolineCar | 最終 | 否 | Car | 實作 RefuelOrCharge() |
| Boat | 最終 | 否 | Vehicle | 實作 Move() |
4. 用抽象打造可擴展應用
實用好處:
- 程式碼彈性和可擴展: 你可以加新類別(比如新動物、交通工具或員工),不用改現有 code。你的迴圈/方法會自動支援新物件 — 只要它實作抽象定義的方法就好。
- 減少重複程式碼: 共用屬性和方法(像名字、基礎邏輯)只要在抽象類別定義一次,子類自動繼承。
- 大型框架和系統常見做法: 比如 ASP.NET MVC 有抽象基礎控制器(ControllerBase),WinForms 有所有 UI 元件的抽象類別 Control。這讓你可以擴充 framework 而不會破壞已經寫好的部分。
哪裡用得到?
- 任何大型系統: 銀行 app(員工、操作、交易)、遊戲(遊戲物件、角色)、商業 app(目錄、商品、用戶)、任何階層或結構化資料。
- 技術面試: 能用抽象建類別、解釋階層結構,是面試常見題。
- 程式架構: 抽象讓你在寫邏輯前就能搭好「骨架」— 適合團隊開發、TDD(測試驅動開發)、還有維護性。
5. 建構階層時常見錯誤與陷阱
錯誤一:忘記實作抽象方法。
如果你沒實作基礎類別的所有抽象成員,編譯器不會讓你建立子類實例。這是硬規定 — 想要具體就要全部實作。
錯誤二:想從多個抽象類別繼承。
C# 不允許同時繼承多個類別,就算都是抽象類別也不行。這是語言限制。如果你想「繼承」多個來源的行為 — 請用介面。介面就像輕量版抽象類別,沒有實作。
錯誤三:不懂中間抽象類別的意義。
這種類別常用來整理共用邏輯,避免產生「不完整」的物件。比如 Car 可以是抽象的,這樣就不會有人直接建立「車」,而是要指定是汽油、柴油還是電動車。
GO TO FULL VERSION