CodeGym /課程 /C# SELF /多個介面的實作

多個介面的實作

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

1. 為什麼要實作多個 interface?

當你在設計一個真實系統時,物件通常不只扮演一個「角色」,而是同時扮演好幾個。想像一下:你有一本電子書,不只能閱讀,還能編輯,甚至可以存到雲端。用物件導向程式設計(OOP)的術語來說,就是這個物件要同時實作好幾個 interface:

  • IReadable — 讀取內容。
  • IWritable — 編輯內容。
  • ISyncable — 跟遠端儲存同步。

這時候多個 interface 的實作就派上用場啦!這是 C#(還有 OOP 整體)的一個超能力,class 沒有這功能:你不能同時繼承兩個以上的 class,但 interface 你想實作幾個都可以。

生活中的比喻

想像公司裡的一個員工。Vasya 可以同時是:

  • 工程師(寫 code)
  • 測試員(偶爾檢查別人的 code)
  • 經理(規劃 sprint 或一天要喝幾杯咖啡)

這些「角色」的職責完全不同。但 Vasya 都能搞定每一份「合約」!寫程式也是一樣:class 實作 interface,然後就要負責它們的「職責」。

2. 多個 interface 的實作語法

超簡單:宣告 class 時用逗號分隔就好:


public interface IReadable
{
    void Read();
}

public interface IWritable
{
    void Write(string text);
}

public class Note : IReadable, IWritable
{
    private string content = "";

    public void Read()
    {
        Console.WriteLine("備忘錄: " + content);
    }

    public void Write(string text)
    {
        content = text;
        Console.WriteLine("備忘錄已更新!");
    }
}

這個例子裡,Note class 同時實作了兩個 interface。所以它一定要有兩個 method 的實作:ReadWrite

3. 用「我們」的 app 當例子

在之前的例子裡,我們做了一個簡單的銀行 app 來處理帳戶。現在假設:我們需要一個通用的文件 class,可以列印(IPrintable)、存檔(ISavable),還有可能寄到 e-mail(IEmailable)。

先定義 interface:


public interface IPrintable
{
    void Print();
}

public interface ISavable
{
    void Save(string filePath);
}

public interface IEmailable
{
    void Email(string toAddress);
}

一次全包的 class:


public class Statement : IPrintable, ISavable, IEmailable
{
    public string Content { get; set; }

    public void Print()
    {
        Console.WriteLine("列印對帳單...");
        Console.WriteLine(Content);
    }

    public void Save(string filePath)
    {
        // 用標準函式庫的 File.WriteAllText。
        File.WriteAllText(filePath, Content);
        Console.WriteLine($"對帳單已存到檔案: {filePath}");
    }

    public void Email(string toAddress)
    {
        Console.WriteLine($"對帳單已寄到信箱: {toAddress}(模擬)");
    }
}

這個 class 現在可以隨便用在 code 裡:


var stat = new Statement { Content = "本月操作:+1000 單位,-500 單位。" };
stat.Print();
stat.Save("statement.txt");
stat.Email("boss@bank.corp");

順帶一提,實務上很方便把這種物件傳給只需要某個 interface 的 method,不用管它還有什麼功能。比如列印的 method 只要 IPrintable,根本不用知道裡面還有「存檔」或「寄信」。

4. 用 interface reference:我的物件會什麼功能,外面看得到嗎?

這裡開始有點魔法。你實作了多個 interface 之後,可以只把物件當成其中一個「合約」來用。比如:


IPrintable printable = new Statement { Content = "抽象化講座" };
printable.Print(); // 只能列印

// printable.Save("file.txt"); // 錯誤:IPrintable interface 不知道 Save。

但如果你切換到另一個 interface:


ISavable savable = printable as ISavable;
if (savable != null)
{
    savable.Save("file.txt");
}

這很方便,當你把物件傳給只需要某個 interface 的 method。這種做法可以降低耦合,讓 code 超靈活:你可以加新實作,不用改舊 code。

5. 多個 interface 實作時,不同 interface 有同名 method 怎麼辦?

這裡就有趣了!如果兩個 interface 都要一個同名 method,但意思不一樣怎麼辦?比如,想像咖啡機的 interface:


public interface IStartable
{
    void Start();
}

public interface IRunnable
{
    void Start();
}

咖啡機可以是「可啟動的」(IStartable — 開始煮咖啡),也可以是「可運行的」(IRunnable — 進入工作狀態)。

預設(一般)實作:


public class CoffeeMachine : IStartable, IRunnable
{
    public void Start()
    {
        Console.WriteLine("咖啡機兩種角色都啟動!");
    }
}

這種情況下,一個 Start() 實作同時滿足兩個 interface。

如果要不同意思呢?

可以用明確 interface 實作


public class CoffeeMachine : IStartable, IRunnable
{
    void IStartable.Start()
    {
        Console.WriteLine("啟動:開始煮飲料!");
    }

    void IRunnable.Start()
    {
        Console.WriteLine("啟動:機器進入工作模式。");
    }
}

這時候只能用 interface 變數才能呼叫特定實作:


CoffeeMachine cm = new CoffeeMachine();

IStartable startable = cm;
startable.Start(); // 啟動:開始煮飲料!

IRunnable runnable = cm;
runnable.Start(); // 啟動:機器進入工作模式。

// cm.Start(); // 編譯不過!Start 不能直接用 class 變數叫。

這招在 .NET 標準函式庫很常見——比如一個 class 同時實作多個不同 framework 的類似 interface,每個都要不同行為。

6. 多個 interface 實作 vs. 繼承的差異

功能 Class(繼承) Interface
基底型別數量 只能一個 隨你加幾個
繼承 code 可以(有基底實作) 不行,只有簽名(除非有預設 method)
儲存狀態 可以 不行
加新角色 不行(或很麻煩,要用 composition) 超簡單,直接實作 interface
適合… 「實體」階層 「角色」/邏輯功能

7. 實務應用:「混合」物件

靠多個 interface 實作,你可以做出有獨特「角色」組合的 class,完全不用煩惱多餘的繼承。

比如我們的銀行 app——可以做一個 class,既能存自己資訊、又能驗證自己、還能列印——全部用不同 interface:


public interface IValidatable
{
    bool Validate();
}

public class Check : IPrintable, ISavable, IValidatable
{
    public string Data { get; set; }

    public void Print()
    {
        Console.WriteLine("列印支票: " + Data);
    }

    public void Save(string filePath)
    {
        File.WriteAllText(filePath, Data);
        Console.WriteLine("支票已存檔: " + filePath);
    }

    public bool Validate()
    {
        return !string.IsNullOrEmpty(Data);
    }
}

這種做法讓你可以很輕鬆擴充架構,class 需要什麼角色就加什麼 interface。如果要加新「職責」——直接實作新 interface 就好。

8. 常見錯誤與陷阱

新手常犯的錯:忘記實作 interface 的所有成員。編譯器不會放過你——會丟錯誤,還會提示你少了什麼。這種錯誤很好懂:「Class 必須實作 interface 成員」。

第二個常見問題——搞混 method 的可見性。如果你是明確實作 method,那它不能用 class 變數叫——只能用 interface。

還有一個常見情境——重構:你改了 interface(比如加了 method),但沒更新所有實作。這會導致編譯錯。設計時盡量不要在很多 class 用了之後再改 interface。

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