CodeGym /課程 /C# SELF /實戰中使用介面

實戰中使用介面

C# SELF
等級 24, 課堂 4
開放

1. 商業邏輯中的介面:按鈕和動作

這是介面主題的最後一堂課,所以這裡會有超多實戰範例,讓你更懂介面在實務上的用法。

介面在大型應用中特別有價值。所以下面舉的都是比較複雜的例子,有時甚至超前課程進度。有興趣就繼續看!沒興趣就直接跳下個等級 :P

最簡單的例子 — 按鈕和點擊

我們繼續開發我們的學習用主控台小程式——假設我們有一個簡單的使用者元件系統(像是按鈕、文字欄位、切換開關)在主控台選單裡。

我們希望有些元件能對點擊有反應(像按鈕),有些則不能(像靜態標題)。介面可以很優雅地實現這種需求。

步驟 1:建立介面


// 可以被「點擊」的元件:
public interface IClickable
{
    void Click();
}

步驟 2:把介面套用到不同型態的元件


// 所有選單元件的基底類別
public class MenuItem
{
    public string Title { get; set; }

    public MenuItem(string title)
    {
        Title = title;
    }

    public virtual void Display()
    {
        Console.WriteLine(Title);
    }
}

// 按鈕:可以「點擊」
public class Button : MenuItem, IClickable
{
    public Button(string title) : base(title) {}

    public void Click()
    {
        Console.WriteLine($"[按鈕] {Title} 被按下了!");
    }
}

// 純文字標籤:不能點擊
public class Label : MenuItem
{
    public Label(string title) : base(title) {}
}

步驟 3:用介面多型


IClickable[] clickableItems = new IClickable[]
{
    new Button("儲存"),
    new Button("離開")
    // Label 不能加進來——它不是 IClickable!
};

foreach (var item in clickableItems)
{
    item.Click();
}

看到魔法了嗎?這個清單只收「會」Click 的物件,所以你不會在執行時遇到「點錯東西」的錯誤。

2. 介面與「策略」模式:動態選擇演算法

假設你寫了一個應用程式,可以用不同方式儲存報表:存檔案、存資料庫、丟到「雲端」,甚至傳到你老闆的 Telegram(別問,什麼都有可能)。

步驟 1:定義需求

我們要讓 ReportGenerator 這個類別能用任何儲存方式,不用知道細節。

步驟 2:描述策略介面


public interface IDataSaver
{
    void Save(string reportData);
}

步驟 3:實作不同的方式


public class FileDataSaver : IDataSaver
{
    public void Save(string reportData)
    {
        Console.WriteLine("[FileDataSaver] 儲存到檔案...\n" + reportData);
        // 這裡可以寫 File.WriteAllText(...)
    }
}

public class DatabaseDataSaver : IDataSaver
{
    public void Save(string reportData)
    {
        Console.WriteLine("[DatabaseDataSaver] 儲存到資料庫...\n" + reportData);
        // 這裡可以寫資料庫存取程式碼
    }
}

步驟 4:用介面切換儲存策略


public class ReportGenerator
{
    private readonly IDataSaver _dataSaver;

    public ReportGenerator(IDataSaver dataSaver)
    {
        _dataSaver = dataSaver;
    }

    public void GenerateReport()
    {
        string report = "這是很重要的報表!";
        Console.WriteLine("產生報表中...");
        _dataSaver.Save(report);
    }
}

靈活示範


// 可以不用改 ReportGenerator 就換儲存方式!
IDataSaver fileSaver = new FileDataSaver();
ReportGenerator fileReport = new ReportGenerator(fileSaver);
fileReport.GenerateReport();

IDataSaver dbSaver = new DatabaseDataSaver();
ReportGenerator dbReport = new ReportGenerator(dbSaver);
dbReport.GenerateReport();

實用重點:如果明天你想把報表寫到什麼超新潮服務,只要寫一個新類別實作介面,然後把物件塞進架構裡,舊程式碼一行都不用改。

3. .NET 裡的介面:IDisposable 和 using

.NET 裡最常見的介面之一就是 IDisposable。所有會用到非受控資源的類別(像檔案、串流、網路連線)都會實作它。

為什麼要有 IDisposable?

當你用到一定要釋放的資源(像關檔案),你就實作 IDisposable,寫一個 Dispose 方法。這樣你就可以把物件丟進 using,離開區塊時 Dispose 會自動被呼叫。

範例:模擬檔案操作

public class FakeFile : IDisposable
{
    public string FileName { get; }

    public FakeFile(string fileName)
    {
        FileName = fileName;
        Console.WriteLine($"開啟檔案: {fileName}");
    }

    public void Dispose()
    {
        Console.WriteLine($"關閉檔案: {FileName}");
    }
}

// 在主程式裡:
using (var file = new FakeFile("otchet.txt"))
{
    Console.WriteLine("寫入檔案中...");
    // 離開 using 區塊時檔案會自動「關閉」
}

輸出:

開啟檔案: otchet.txt
寫入檔案中...
關閉檔案: otchet.txt

真的很有用:你不會忘記關檔案——介面跟語言機制會保護你。

4. 集合和 LINQ 裡的介面

你在用清單、陣列、字典時,其實早就在用介面了,只是沒特別注意。


List<int> list = new List<int> { 1, 2, 3 };
IEnumerable<int> enumerable = list; // 沒問題!

// 現在可以這樣遍歷元素:
foreach(var x in enumerable)
{
    Console.WriteLine(x);
}

大部分 LINQ 方法都是透過 IEnumerable<T> 這個介面操作集合。這讓你的程式碼不用管集合的實際型態。

為什麼要這樣?
你可以把 List<T> 換成 T[]、HashSet<T>,甚至你自己寫的集合——你的程式碼都還是能跑!

5. 測試用的介面(Mock 物件)

寫測試時很重要的一點是隔離程式碼,不要碰到真的資料庫或檔案。介面讓你可以用假物件(mock)來測試,不會動到真的資料或檔案。


public class FakeDataSaver : IDataSaver
{
    public bool WasCalled { get; private set; } = false;
    public void Save(string reportData)
    {
        WasCalled = true;
        Console.WriteLine("已經把資料存到 mock 物件!");
    }
}

// 在測試裡
FakeDataSaver saver = new FakeDataSaver();
ReportGenerator generator = new ReportGenerator(saver);
generator.GenerateReport();

Console.WriteLine($"Save 有被呼叫嗎? {saver.WasCalled}");

結果:測試不會碰到真實世界,但可以檢查方法有沒有被呼叫!

6. 明確實作介面來解決衝突

有時候一個類別要實作兩個有同名方法的介面,但這些方法邏輯不一樣。


public interface IFlyable
{
    void Move();
}

public interface ISwimmable
{
    void Move();
}

public class Duck : IFlyable, ISwimmable
{
    // 明確實作
    void IFlyable.Move()
    {
        Console.WriteLine("鴨子在飛!");
    }

    void ISwimmable.Move()
    {
        Console.WriteLine("鴨子在游!");
    }
}

Duck duck = new Duck();

// duck.Move(); // 錯誤:沒有這個方法!
((IFlyable)duck).Move(); // "鴨子在飛!"
((ISwimmable)duck).Move(); // "鴨子在游!"

這看起來有點硬,但有時規格就是這樣要求!

7. 事件模型裡的介面:INotifyPropertyChanged

.NET 有標準的事件介面——像是資料模型有變動時 UI 要知道(WPF、MAUI、其他 GUI 都很常見)。


using System.ComponentModel;

public class Person : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
}

重點:任何支援資料繫結的框架都會希望你的類別實作這個介面——這樣 UI 屬性變動時會自動更新。

8. 工廠模式(Factory)裡的介面

介面超適合拿來做工廠——就是那種會生出不同但可互換物件的類別。


public interface ITransport
{
    void Move();
}

public class Bicycle : ITransport
{
    public void Move() => Console.WriteLine("騎腳踏車!");
}

public class Car : ITransport
{
    public void Move() => Console.WriteLine("開車!");
}

public class TransportFactory
{
    public static ITransport Create(string type)
    {
        return type switch
        {
            "bike" => new Bicycle(),
            "car" => new Car(),
            _ => throw new ArgumentException("未知的交通工具類型")
        };
    }
}

ITransport transport = TransportFactory.Create("bike");
transport.Move(); // "騎腳踏車!"

9. 事件用的介面:自訂事件範例

來描述一個「事件監聽者」介面:


public interface ILoginListener
{
    void OnLogin(string userName);
}

// 會觸發事件的類別
public class LoginManager
{
    private List<ILoginListener> listeners = new();

    public void Subscribe(ILoginListener listener) => listeners.Add(listener);

    public void Login(string userName)
    {
        Console.WriteLine($"使用者 {userName} 登入了。");
        foreach (var listener in listeners)
            listener.OnLogin(userName);
    }
}

// 實作介面的類別
public class WelcomeMessage : ILoginListener
{
    public void OnLogin(string userName)
    {
        Console.WriteLine($"歡迎, {userName}!");
    }
}

LoginManager manager = new();
manager.Subscribe(new WelcomeMessage());
manager.Login("Vasya");
// 使用者 Vasya 登入了。
// 歡迎, Vasya!

面試時:如果被問到「你會怎麼做自己的事件系統?」就大方講介面監聽者這一套!

10. 插件用的介面(應用程式擴充性)

很多大型應用都支援插件。靠介面,你的程式可以「即時載入」新模組,完全不用知道它們的內部細節。


public interface IPlugin
{
    string Name { get; }
    void Run();
}

// 你的應用程式:
public class PluginLoader
{
    public void LoadAndRun(IEnumerable<IPlugin> plugins)
    {
        foreach (var plugin in plugins)
        {
            Console.WriteLine($"啟動插件: {plugin.Name}");
            plugin.Run();
        }
    }
}

插件可以給第三方開發——只要有實作介面就好。你的應用程式就能無限擴充!

2
任務
C# SELF, 等級 24, 課堂 4
上鎖
實作幾何圖形的介面
實作幾何圖形的介面
2
任務
C# SELF, 等級 24, 課堂 4
上鎖
使用介面實作簡單工廠
使用介面實作簡單工廠
1
問卷/小測驗
介面使用方式,等級 24,課堂 4
未開放
介面使用方式
進階介面
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION