###### tags: `C#` # 介面(Interface) ## 說明 介面可以視為只包含抽象成員的一種特殊類別,它只定義成員的介面規格,成員內容則由實作介面的衍生類別根據自身需求提供專屬的實作。**介面最大的好處在於將方法成員的規格與實作分開,解決了類別繼承所造成的問題。** 繼承機制儘管威力強大,卻相對的衍生出其他的問題,整個繼承關係裏,衍生類別同時被允許存取基礎類別`public`以及`protected`層級的相關成員,當繼承架構日益複雜,基礎類別的修改,往往對大量的衍生類別形成不同程度的影響,造成大型系統日後演進的因難,同時降低系統擴充的彈性。 介面將成員規格與實作分開,將類別公用介面從實作中抽離出來,成為獨立不含方法實作的純抽象類別,因此當你定義一個繼承介面的衍生類別,只需遵守方法的宣告語法,再自行定義專屬的方法實作,如此一來即可避免基礎類別與衍生類別之間因為繼承所帶來的問題,另外一方面,由於繼承介面類別可以完全自由實作介面定義的方法內容,在某種程度上亦達到了多型的目的。 * **介面為只有宣告成員,而沒有實作的類別** * **介面只能定義屬性、方法、事件、索引,且不包含實作(Implementation)這些成員的程式碼** * **介面可以有 0 個或多個成員** * **介面的成員只能是屬性、方法、事件、索引** ```csharp= interface 介面 { int 屬性 { get; } //屬性 public void 方法();//方法 event MyEvent 事件;//事件 int this[int index] {get; set;}//索引 } ``` * **一個類別只能繼承一個父類別,但可以實作(繼承)多個介面** ```csharp= class 類別 : 介面1, 介面2, 介面3 { xxxxx } ``` * **介面可以繼承多個介面** * **介面不可以被實體化 ⇒ 即不可以 new 介面** * **若類別繼承介面,則要實作介面的所有成員** ## 宣告介面 由於介面本身需由外部類別實作其所有方法,因此所有介面方法一律均為 **public** 層級,**不需使用存取修飾詞**,當你使用存取修飾詞於方法宣告時,將會產生修飾詞無效的編譯錯誤。 ```csharp= interface 介面名稱 { // interface 規格定義 … } ``` ## 實作介面 **實作介面的類別必須實作其所有方法成員,也就是必須表現介面所有的行為,即使沒有實作其內容,還是必須在實作類別裏,定義介面的方法。** ![](https://i.imgur.com/0bDdWA9.png) 上圖左邊是一個包含兩個方法成員定義的介面 A,由於這是一個介面,因此其中的方法成員只定義而沒有任何實作內容,右邊則是實作此介面的兩個類別: B 與 C 。 類別 B 在功能面上,只需提供介面 A 方法成員`aMethod`的實作,但是由於其實作了介面,因此必須同時定義`bMethod`,即便只是定義此方法無任何實作內容,類別 C 亦同時實作了介面 A 與其所有成員。 ## 實作要點 1. **分開方法的定義與實作**:介面抽離方法的定義與實作,你可以將其視為一種完全只有抽象方法的純抽象類別。 2. **避免繼承架構的相依性**:介面最大的好處在於避免繼承架構關系裏,基礎類別與衍生類別之間緊密的相依性,提供最大的設計彈性。 3. **公開所有成員**:所有的介面方法成員均只包含方法名稱,且一律為 public 存取層級。 4. **實作所有介面成員**:繼承介面的衍生類別必須實作所有的介面成員,而且成員的名稱、參數、傳回值型別都必須完全符合,你可以實作一個不包含任何內容的方法,避免編譯錯誤,但是無論如何,衍生類別一定要完成所有的介面方法實作。 ## 範例 1. 建立介面型別`IMeasure`,`Length`、`Area`以及`Volume`是三個介面方法規格,後續實作此介面的類別必須依據這裏的定義,完成三個方法的內容實作。 ```csharp= interface IMeasure { void Length(double len); void Area(double len); void Volume(double len); } ``` 2. 類別`Square`實作`IMeasure`介面,此類別實作三個介面定義的方法成員`Length`、`Area`以及`Volume` ```csharp= class Square : IMeasure { public void Length(double len) { double squareLength = 4 * len; Console.WriteLine($"邊長為 {len} 的正方形周長 = {squareLength}"); } public void Area(double len) { double area = Math.Pow(len, 2); Console.WriteLine($"邊長為 {len} 的正方形面積 = {area}"); } public void Volume(double len) { double volume = Math.Pow(len, 3); Console.WriteLine($"邊長為 {len} 的立方體體積 = {volume}"); } } ``` 3. 建立`Square`物件實體`square`,然後引用實作介面的方法成員 ```csharp= class Program { static void Main(string[] args) { Square square = new Square(); IMeasure myIMeasure = square ; myIMeasure.Length(5); myIMeasure.Area(5); myIMeasure.Volume(5); Console.ReadLine(); } } ``` 4. 執行結果 ```htmlembedded= 邊長為 5 的正方形周長 = 20 邊長為 5 的正方形面積 = 25 邊長為 5 的立方體體積 = 125 ``` ## 多型(Polymorphism) 1. 建立介面型別`IMeasure`,定義`Length`方法 ```csharp= interface IMeasure { void Length(double len); } ``` 2. 類別`Square`實作`IMeasure`介面,實作介面定義的方法成員`Length` ```csharp= class Square : IMeasure { public void Length(double len) { double squareLength = 4 * len; Console.WriteLine($"邊長為 {len} 的正方形周長 = {squareLength}"); } } ``` 3. 類別`Circle`實作`IMeasure`介面,實作介面定義的方法成員`Length` ```csharp= class Circle : IMeasure { public void Length(double r) { double circleLength = 2 * Math.PI * r; Console.WriteLine($"邊長為 {r} 的圓周長= {circleLength}"); } } ``` 4. 方法`LengthMeasure`接受任何`IMeasure`介面的實作類別物件以及要計算的長度值,於方法中透過`measure`引用`Length`方法完成邊長計算,由於所有`IMeasure`介面的實作類別一定實作`Length`方法,因此傳入任何實作物件都可以正常執行。 ```csharp= class Program { static void Main(string[] args) { IMeasure square = new Square(); LengthMeasure(square, 10); Circle circle = new Circle(); LengthMeasure(circle, 10); Console.ReadKey(); } static void LengthMeasure(IMeasure measure, double len) { measure.Length(len); } } ``` > 參考資料 * [介面](http://www.kangting.tw/2018/11/blog-post_27.html) * [介面多型設計](http://www.kangting.tw/2018/11/blog-post_30.html) * [小山的 C# 教學-Interface](https://www.youtube.com/watch?v=ODrhZ1v63-s&list=WL&index=7)