CodeGym /課程 /C# SELF /抽象方法與屬性

抽象方法與屬性

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

1. 抽象方法

在上一堂課我們打好了基礎:認識了抽象類別,也看到它們可以包含抽象方法和屬性。我們明白這是一種「契約」,強制繼承的類別一定要自己實作這些東西。

現在我們來深入一點,看看更複雜、更實用的情境。這堂課會聚焦在語法細節、抽象在多層繼承裡怎麼運作,還有什麼時候該用 abstract,什麼時候該用 virtual

抽象方法就像一本老食譜裡的神祕配方:「加點祕密材料」——到底是什麼沒說,但後面的廚師都得自己想辦法!

抽象方法一定要在抽象類別裡

如果你想在一般(非抽象)類別裡宣告抽象方法,編譯器會直接報錯。為什麼?因為一般類別可以直接 new 出來,如果你呼叫一個沒寫內容的方法,會發生什麼事?只會得到 IDE 一臉問號。


// 這會編譯失敗:
public class WrongClass
{
    public abstract void Oops(); // 這樣不行!
}

只要類別裡有一個抽象方法,它就一定要標成 abstract

在子類別實作抽象方法

實作時要用 override 關鍵字。如果你繼承抽象類別但沒實作所有抽象方法,那這個類別也必須是抽象的(不然編譯器會抗議)。

繼續我們的應用程式:

在前面的課程我們做了一個 Shape(圖形)類別,裡面有個抽象方法算面積。現在來做幾個具體的圖形:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

為什麼抽象方法這麼重要?

抽象方法就是你在說:「各位,如果你要繼承這個類別,請自己決定這東西怎麼做!」這讓程式架構更清楚,避免漏掉重要東西,最重要的是可以讓多型(polymorphism)發揮到極致。

2. 抽象屬性(properties)

什麼是抽象屬性

抽象屬性就跟抽象方法一樣是契約,只是針對屬性(property)。對 C# 來說這很自然,因為屬性是封裝資料的主要方式。抽象屬性就是:「讓子類自己決定這個屬性值要怎麼來(甚至怎麼設定)」。

範例:
給圖形加個 Name 屬性——每個子類都要有,但怎麼回傳由自己決定。


public abstract class Shape
{
    public abstract string Name { get; }

    public abstract double CalculateArea();
}

現在每個子類都必須實作這個屬性:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }
    public override string Name => "矩形";

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }
    public override string Name => "圓形";

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

語法特色

抽象屬性跟 interface 很像:宣告時沒有內容,但要指定是只有 getter,還是也有 setter


public abstract class Creature
{
    // 唯讀屬性
    public abstract string Species { get; }

    // 可讀可寫屬性
    public abstract int Age { get; set; }
}

在實作類別裡可以自己決定怎麼取得(或設定)這個值。

為什麼要用抽象屬性

抽象屬性超適合你想讓每個子類自己決定怎麼取得或存這個值。這在建模、商業邏輯、view model,或是屬性邏輯比單純回傳欄位還複雜時很常用。

比如有的圖形名字要算出來,有的直接回傳常數,都可以用抽象屬性「藏」起來。

3. 圖解:抽象類別、方法和屬性的關係

來看個圖,感受一下怎麼運作:


┌─────────────┐
│ abstract    │
│   Shape     │
│-------------│
│ +Name: str  │  <-- 抽象屬性
│ +Area(): dbl│  <-- 抽象方法
└─────┬───────┘
      │
 ┌────▼────┐      ┌───────┐
 │Rectangle│      │ Circle│
 │........ │ .... │ ......│
 │+Name    │      │+Name  │
 │+Area()  │      │+Area()│
 └─────────┘      └───────┘

這樣我們就能用同一個介面操作任何 Shape 子類,但每個子類「底下」怎麼做都沒差。

UML 圖:抽象方法和屬性


┌────────────────────────────┐
│        abstract class      │
│           Animal           │
│────────────────────────────│
│+ Name: string {abstract}   │
│+ MakeSound(): void {abstract}│
└─────────────┬──────────────┘
              │
     ┌────────┴──────────┐
     │                   │
┌────────────┐     ┌─────────────┐
│    Cat     │     │    Dog      │
│────────────│     │─────────────│
│+ Name      │     │+ Name       │
│+ MakeSound()│    │+ MakeSound()│
└────────────┘     └─────────────┘

4. 實務情境與專案好處

抽象方法和屬性就是你設計會進化的類別繼承時的利器。這是大型商業應用、多層領域模型、plugin 系統、UI framework 的基礎,讓系統底層有「規則」,所有子類都得遵守。

在真實專案裡,這種「純」架構設計讓你幾個月、幾年後加新功能時很安心,因為舊程式早就考慮到所有行為。

「為什麼」與多型的回歸

多型看起來像魔法,其實就是契約:「你可以對任何這家族的物件呼叫某個方法,一定會得到正確結果」。抽象方法和屬性就是讓整個繼承鏈都講同一種語言,但不用硬塞預設實作。

這種做法在 plugin 系統(IDE、繪圖軟體、CRM 系統)很常見,外部開發者寫擴充時,必須實作平台要求的最小功能。例如你寫檔案處理模組,基底類別可以有抽象屬性 FileExtension 和抽象方法 Open(),這樣任何 plugin 都能正確處理自己的檔案類型。

抽象、虛擬和一般類別成員的差異

特性 一般方法/屬性 virtual abstract
有沒有實作 沒有
override 必須嗎 不用 不用(可選) 要(子類必須)
能不能直接呼叫 可以 可以 不行
能不能在一般類別裡 可以 可以 不行
能不能標 sealed 不行 可以 不行

5. 在我們的學習應用裡的應用

現在有了新知識,回到我們的圖形應用,看看抽象屬性和方法怎麼一起打造彈性又好懂的系統。


// 例如在主程式裡
List<Shape> shapes = new List<Shape>
{
    new Rectangle(4, 5),
    new Circle(2.5)
};

foreach (Shape shape in shapes)
{
    // 靠抽象屬性 Name 和方法 CalculateArea,
    // 我們不用管圖形的實際型別
    Console.WriteLine($"{shape.Name}, 面積: {shape.CalculateArea():F2}");
}

結果:

矩形, 面積: 20.00
圓形, 面積: 19.63

很美吧:程式根本不在意圖形是誰——只要呼叫屬性和方法,.NET CLR 會自動找到正確的實作!

6. 常見錯誤與實作細節

錯誤一:沒實作父類的抽象方法。
如果子類有任何抽象方法或屬性沒實作,自己又沒標 abstract,編譯器就不讓你過。這很有用:你不會 new 出一個「有漏洞」的物件——比如沒有 CalculateArea() 的圖形。

錯誤二:方法標成 abstract,但類別沒標。
這種也不能編譯。如果你加了 abstract 方法,類別本身也要是抽象的:


public abstract class Polygon : Shape
{
    // 沒實作 CalculateArea(),類別還是 abstract
}

錯誤三:想用 virtual 來實作抽象方法。
抽象方法只能用 override 來實作,不能用 virtual。但如果你想讓後面的子類還能再改寫,可以先 override,然後在子類裡標成 virtual


public override double CalculateArea()
{
    // 預設實作...
}

更深一層的子類就能再 override 這個方法。但不能再標成 abstract,因為已經有實作了。

2
任務
C# SELF, 等級 22, 課堂 2
上鎖
宣告抽象類別與方法
宣告抽象類別與方法
2
任務
C# SELF, 等級 22, 課堂 2
上鎖
抽象方法與屬性
抽象方法與屬性
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION