CodeGym /課程 /C# SELF /介面概念與語法

介面概念與語法

C# SELF
等級 23 , 課堂 0
開放

1. 什麼是介面?

如果說抽象類別是有部分實作的「半成品」模板,那麼介面就是一份純粹的需求清單:某個東西(類別)必須要會什麼,才能在某個抽象情境下被用起來。

在寫程式時,介面就像是一份「合約」或「需求清單」,規定物件要有什麼行為。它描述了一組公開的方法、屬性、索引子和事件,實作這個介面的類別一定要提供這些東西。介面就像在說:「如果你想叫做 IDriveable(能開的),那你就要給我 Drive()Stop() 這兩個方法。」

你可以把介面想像成員工需求清單:比如你要找「廚師」,那介面上就會寫:「要會煮菜、上菜、洗手」。至於每個廚師怎麼做,隨便他,只要對外看起來都符合合約就好。

重點:介面只描述要有什麼,不管怎麼做

用 OOP 術語簡單說

  • 介面描述物件的外觀(API):可以做哪些事。
  • 沒有狀態(沒有欄位)。傳統上介面也不能有實作,不過新版 C# 有例外(像預設方法),這個之後會細講。
  • 一個類別可以實作無限多個介面(不像類別繼承只能一個!)。

生活中的比喻

USB 埠—大家都知道可以插滑鼠、鍵盤、隨身碟、耳機,甚至咖啡機(真的有這種東西!)。滑鼠裡面怎麼做不重要,只要有 USB 插頭、支援對的協定就行。這就是介面啦!

介面有什麼用?

  • 降低耦合。程式只跟介面互動,不跟具體類別綁死。寫程式要「針對介面」而不是「針對實作」。
  • 可以多重繼承行為:一個類別可以實作好幾個介面。
  • 標準化:可以做通用機制:比如所有可以比較的物件都實作 IComparable
  • 好測試:測試時很容易用「假物件」取代真實實作。
  • 好擴充:可以加新「模組」不用動到舊程式。

2. 介面宣告語法

來看程式碼吧!在 C# 裡面,介面用 interface 關鍵字宣告,名字通常都用 I 開頭:


// 介面宣告
public interface IPrintable
{
    // 抽象方法—合約
    void Print();

    // 也可以宣告屬性
    string Name { get; set; }
}

記住:介面的 method 跟 property 都沒有實作(沒有方法本體,只有簽名—跟抽象方法一樣)。

介面重點

  • 不能在介面裡宣告欄位(成員變數)。
  • 所有方法、屬性、事件、索引子預設都是 public(實作時也要是 public)。
  • 介面不能有建構子(因為沒有狀態)。
  • 介面不管實作它的類別是抽象還是具體都可以。

介面實戰

來把介面加到我們的學習小專案。假設我們現在有一個「可列印」介面(IPrintable),由 Report 類別實作,還有一個新類別 Invoice

public interface IPrintable
{
    void Print();
    string Name { get; set; }
}

現在來定義一個實作這個介面的類別:

public class Report : IPrintable
{
    public string Name { get; set; }

    public Report(string name)
    {
        Name = name;
    }

    // 實作介面的方法
    public void Print()
    {
        Console.WriteLine($"列印報告: {Name}");
    }
}

再來一個完全不同的類別,但用同一個介面:

public class Invoice : IPrintable
{
    public string Name { get; set; }

    public Invoice(string name)
    {
        Name = name;
    }

    public void Print()
    {
        Console.WriteLine($"列印發票: {Name}");
    }
}

現在我們可以寫一個方法,能處理任何可列印的東西:

public static void PrintAnything(IPrintable printable)
{
    printable.Print(); // 就這樣!不管是報告還是發票,只要會列印就行。
}

來看一下怎麼用:

var report = new Report("每月報告");
var invoice = new Invoice("發票 #12345");

PrintAnything(report);  // 列印報告: 每月報告
PrintAnything(invoice); // 列印發票: 發票 #12345

這樣介面就能讓你寫出通用、可擴充又漂亮的程式碼啦。

3. 介面實作

類別實作介面

類別用冒號來實作介面(對,跟繼承一樣):

public class Ticket : IPrintable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine($"列印票券: {Name}");
    }
}

重點:類別一定要實作介面的所有成員,而且要 public

如果漏掉任何一個成員:

public class BrokenTicket : IPrintable
{
    // 少了 Print() 的實作
    public string Name { get; set; }
}
// 編譯錯誤: 'BrokenTicket' 沒有實作介面成員 'IPrintable.Print()'

多個介面

類別可以用逗號實作多個介面:

public interface IStorable
{
    void Store();
}

public class MultiPurposeDoc : IPrintable, IStorable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine("列印文件");
    }

    public void Store()
    {
        Console.WriteLine("儲存文件");
    }
}

4. 介面有什麼用?

你現在可能在想:「好啦,語法我懂了。那這些東西在現實世界到底有什麼用,除了讓我這堂課更難過?」我跟你說,介面是專業開發裡最常用的工具之一。

職責分離與低耦合(Decoupling / Loose Coupling):

  • 想像你在寫一個音樂播放器。它不在乎音樂從哪來—本地檔案、網路還是 CD,只要來源能給它音訊串流就好。
  • 你可以定義一個 IAudioSource 介面,裡面有 GetAudioStream() 方法。
  • 然後你會有 FileAudioSourceInternetAudioSourceCDAudioSource 這些類別來實作這個介面。
  • 你的播放器只跟 IAudioSource 互動,不知道也不在乎具體是哪一種。如果明天又有新來源,比如 BluetoothAudioSource,你也不用改播放器的程式碼!只要寫一個新類別實作 IAudioSource 就好。這樣你的系統就超彈性又好擴充。這就是低耦合—元件只依賴抽象(介面),不依賴具體實作。

多型與統一處理:

就像我們剛剛 PrintAnything 的例子,你可以有一堆不同型別的物件,但它們都有共同的行為(介面描述的)。你可以對所有這些物件呼叫同一個方法(Print()),不用管它到底是報告、發票還是票券。這樣就能寫出很精簡又通用的程式碼。

測試(Unit Testing):

這大概是介面最重要的應用之一。你在測試某個元件時,常常會需要其他元件來配合(比如一個存資料的類別,會依賴一個操作資料庫的類別)。

你不用真的傳一個 DatabaseSaver(那你測試還要真的有資料庫!),你可以傳一個「假的」(或「mock」)物件,只要它有實作 IDataSaver 介面就行。這個「mock」物件只會假裝有存資料,根本不會去動真的資料庫。這樣你就能把元件分開測,速度快又不會被外部東西卡住。

API 跟框架開發:

你在寫函式庫或框架時,會想給開發者「擴充點」。介面超適合這種情境。你可以說:「如果你想讓你的元件跟我的系統合作,就實作這個介面吧」。.NET 標準函式庫裡面一堆介面(像 IEnumerable<T>IDisposableIComparable<T>)—它們就是為了定義最常見的合約。

直接針對介面寫程式(Programming to an Interface):

老手常說:「寫程式要針對介面,不要針對實作」。意思就是你在定義變數型別或方法參數時,盡量用介面(IDriveable),不要用具體類別(Car)。這樣你的程式碼就更彈性,也不會被實作細節綁死,想換一個實作也很容易。

5. 用介面時常見錯誤

錯誤一:想要 new 一個介面。
你可以寫 Cat murzik = new Cat("Murzik", 3);,因為 Cat 是具體類別。但你不能ITalkable talker = new ITalkable();。介面只是合約、模板,沒有實作,不能直接 new。這就像藍圖,不是現成的房子。

錯誤二:忘記實作所有介面成員。
如果你說你的類別要實作介面,比如 IMyInterface,那你一定要實作它所有的方法。只要漏一個,編譯就會報錯:MyClass 沒有實作 IMyInterface.TheMissingMethod()

錯誤三:實作時用錯存取修飾詞。
介面的方法預設就是 public,你實作時也一定要是 public。如果你寫成 privateprotected,編譯器會報錯。你答應了就要公開實作。

錯誤四:想在介面裡加欄位或建構子。
介面只描述行為,不描述狀態。所以不能加欄位或建構子。你如果硬加就會編譯錯誤。只能有屬性,而且也只是描述 getter/setter。

錯誤五:把 override 跟介面實作搞混。
override 是拿來覆寫基底類別的方法。實作介面時不用寫 override—你只要寫一個 public 方法,簽名對就好。這個細節很容易搞錯。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION