深入解析 C# 中的 Interface 和 class 種類
亂
在 C# 程式設計的領域中,interface 和 base class, concrete class, abstract class 很容易被搞混,這篇文章記錄我對這些東西的理解。
Base class
base class 是一種 class 讓別的 class 可以從 base class 繼承或擴展。在 C# 中 base class 的 method 可以是 virtual 也可以是有實作的:
public class Shape
{
public virtual double GetArea()
{
throw new NotImplementedException();
}
}
Interface:契約的化身
在 C# 中,interface 可以被理解為一種契約。當一個 class 根據 interface 被實作出 ,它實質上是在承諾將遵循這個契約所定義的一套功能。interface 描述了所有 functions,這些方法們必須被任何實現該 interface 的 class 或 struct 提供具體的實作。並且所有的 interface 裡面的成員都是 public 的。
interface IShape
{
double GetArea();
}
在這個例子中,IShape
interface 定義了一個 GetArea
方法,任何實現這個 interface 的 class 都必須提供 GetArea
方法的具體實作。
為什麼使用 Interface
在 C# 程式設計中,使用 interface 的原因有幾個重要的方面。首先是強制性:interface 強迫工程師實現特定的 method 。這意味著當一個 class 實作了某個 interface,它就必須提供該 interface 所規定的所有成員的具體實作。這種強制實作的特性確保了程式的一致性和可預測性,是確保程式碼品質的一種方式。
其次是多重繼承:在 C# 中,一個 class 可以實作多個 interface,這提供了一種形式的多重繼承。雖然 C# 不支持傳統意義上的多重繼承(一個 class 繼承多個 base class),但通過實作多個 interface,類別可以獲得來自多個 base class 的功能。這增加了程式碼的靈活性和重用性,同時避免了多重繼承可能引起的複雜性和問題。
最後是解耦:interface 有助於降低 class 之間的耦合度。使用 interface 可以使 class 之間的關係更加鬆散,每個 class 都專注於實作它們各自的 interface。這種方式促進了程式碼的模組化,使得每個部分都更加獨立,從而簡化了維護和擴展的工作。
Base class 與 interface
在設計中,我們如果只需要確保被實作的 class 要遵循某種規範,那這個時候使用 interface。一般時候都可以設計成 base class
Concrete class 與 interface
interface IShape
{
double GetArea();
}
public class Circle : IShape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public double GetArea()
{
return Math.PI * Radius * Radius;
}
}
在這個例子中,Circle
是一個具體類別,它實現了 IShape
,並提供了 GetArea
方法的具體實現。由於 Circle
是具體的,您可以創建其實例並使用它。
抽象類別:部分實現的藍圖
抽象類別允許您提供一些方法的實現,同時要求子類別實現其他方法。這種部分實現的特性使得抽象類別在某些情況下比 interface 更有用。
public abstract class Shape
{
public abstract double GetArea();
// 已實作的方法
public void Display()
{
Console.WriteLine("Displaying the shape");
}
}
在這裡,Shape
是一個抽象類別,它要求任何衍生的類別都必須實現 GetArea
方法,但同時它提供了 Display
方法的實現。
為什麼使用抽象類別?
首先,abstract class 只需要部分實作,亦即你可以把剩下的 methods 保留為抽象的 method。這些 method 又可以在別的 class 中實作。
第二,抽象類別強制實作單一繼承,而這樣的作法確保繼承結構的清晰和一致性。避免多重繼承可能帶來的麻煩。
最後,抽象類別在封裝和資料隱藏的控制上做到很好的控制。我們可以決定哪些要 public,哪些要 private。
public abstract class Shape
{
// 抽象方法
public abstract double GetArea();
}
在 Shape
中,GetArea
方法是抽象的,他還沒有任何的實作。任何繼承自 Shape
的 class 都必須提供 GetArea
方法的實作。
如果一個 class 包含抽象方法,那麼這個 class 必須被聲明為 abstract class(使用 abstract
關鍵字)。同時 abstract class 中的 abstract method 不能被實作。
public class Circle : Shape
{
public double Radius { get; private set; }
public Circle(double radius)
{
Radius = radius;
}
// implement base class abstract method
public override double GetArea()
{
return Math.PI * Radius * Radius;
}
}
在 Circle
中,我們提供了 GetArea
方法的具體實作,計算並回傳圓的面積。
這邊要注意的是如果 sub class 沒有實作 GetArea
的話,這個 sub class 必須也是 abstract class。(超亂)
Interface 與抽象類別
什麼時候用 interface,什麼時候用抽象類別?
首先考慮的是功能的設計目的:如果 class 目的是為了定義一個通用的契約的函式,他不需要具體的實作,這種時候就是選擇 interface。反之我們就要設計成抽象類別。
另外在降低耦合方面,interface 有較好的設計的優勢,特別是如果系統各個部分之間要維持一定程度的獨立性的時候。如果你有很多 class 中的功能有重複使用或呼叫的情況,抽象類別的就讓你可以把共同的行為設計在這邊,其他的 class 透過單一繼承來客製化。
base class 與抽象類別
如果你需要這個 class 的 Method 本身有實作,且可以被繼承實作的,就選用 base class,否則就要將這個 class 定義為抽象類別。
Best Practice
在 .NET 中,collection interface(如 IEnumerable
)提供了一個很好的範例來有效地使用 interface。
想像有這樣的一個 interface 讀取資料,一個 class 實作了 interface 來實際讀取資料:
public interface IPeopleRepository
{
IEnumerable<Person> GetPeople();
}
// 假想的資料從資料庫讀取
public class DatabasePeopleRepository : IPeopleRepository
{
public IEnumerable<Person> GetPeople()
{
return new List<Person>();
}
}
然後定義一個函式用來 loop 全部的 people:
static void InterfaceFetch(PeopleRepository peopleRepo)
{
IEnumerable<Person> people = peopleRepo.GetPeople();
foreach (var person in people)
{
Printer.ConsumeArray(person.ToArray());
}
}
這邊可能有人問說,為什麼使用 IEnumerable<Person>?如果直接使用 List<Person>(相對 IEnumerable 來說, List 是 concrete class),限制會變得相對大,因為可能 Printer.ConsumeArray 不一定能接受 List<Person> 型態的參數直接透過 ToArray()。
最後我們在 main 這邊讀取資料,是不是發現居然可以這樣寫:
static void Main(string[] args)
{
IPeopleRepository repo = new DatabasePeopleRepository();
InterfaceFetch(repo);
}
這樣的設計可以很輕鬆切換不同的資料來源,我們只要修改 new DatabasePeopleRepository();
換成別的具體實作的 class 即可。這樣我們在單元測試的時候也很方便可以直接 mock 這個 class。
結語
不知不覺就寫了一大堆,這邊簡單整理一下所有的內容:
在探討 C# 中的類別結構時,我們遇到了 Concrete Class、Abstract Class 和 Interface 之間的重要區別。這些差異不僅影響著我們的程式碼設計,還直接影響到程式碼的穩健性和靈活性。
首先,我們注意到 Concrete Class 在某些情況下可能不是最佳選擇。其主要限制在於無法在編譯時期進行完整性檢查。這意味著如果 Concrete Class 的某些部分未被正確實作或使用,這些問題直到執行時才會顯現,這可能導致程式的不穩定或錯誤。
另一方面, Abstract Class 和 Interface 提供了更強的類型檢查,這種檢查在編譯時進行。這表示我們可以更早地發現問題,從而提高程式碼的品質和穩定性。Abstract Class 與 Interface 在這方面的主要區別在於它們提供的靈活性和用途。
Abstract Class 可以包含已實作的方法,這為 Sub Class 提供了一個共同的基礎和功能。Sub Class 可以直接利用這些已實作的方法,而無需進行重複的程式碼編寫。然而,Sub Class 只能繼承一個抽象類別,並沒有多重繼承的概念。
與此相反,Interface 則提供了更大的靈活性。一個 class 可以實現多個 Interface,這允許它同時遵循多個不同的契約。Interface 本身不包含任何實作,因此它們提供了一個純粹的抽象層。值得注意的是,所有 Interface 成員默認都是公開的,這與 Abstract Class 中可以有 public 與 private 的成員形成鮮明對比。
總結來說,Concrete Class、Abstract Class 和 Interface 在 C# 程式設計中各有其用途和優勢。選擇合適的類型取決於具體的應用場景和需求。理解它們之間的區別才能寫出更高品質、更易於維護和擴展的程式碼。