Home [C# 筆記] 抽象類別(Abstract Class)和介面(Interface)有何不同?
Post
Cancel

[C# 筆記] 抽象類別(Abstract Class)和介面(Interface)有何不同?

抽象類別(Abstract Class)和介面(Interface)有何不同?

相同點:

  1. 都可以被繼承
  2. 都不能被實體化 (不能new)
  3. 都可以包含方法聲明
  4. 衍生類別必須實作未實作的方法

區別:

  1. 抽象基底類別可以定義欄位、屬性、方法實作。介面只能定義屬性、索引器、事件、和方法聲明,不能包含欄位。
  2. 抽象類別是一個不完整的類別,需要進一步細化,而介面是一個行為規範。微軟的自訂介面總是後帶able字段,證明其是表述一類「我能做。。。」
  3. 介面可以被多重實現,抽象類別只能被單一繼承。(抽象類別只能繼承一個類別)
  4. 抽象類別更多的是定義在一系列緊密相關的類別間,而介面大多數是關係疏鬆但都實現某一功能的類別中。
  5. 抽象類別是從一系列相關物件中抽像出來的概念,因此反映的是事物的內部共通點;介面是為了滿足外部呼叫而定義的一個功能約定,因此反映的是事物的外部特性
  6. 介面基本上不具備繼承的任何具體特點,它僅僅承諾了能夠呼叫的方法
  7. 介面可以用來支援回調(callback),而繼承並不具備這個特點
  8. 抽象類別實作的具體方法預設為虛的,但實作介面的類別中的介面方法卻預設為非虛的,當然您也可以宣告為虛的
  9. 如果抽象類別實現接口,則可以把接口中方法映射到抽象類別中作為抽象方法而不必實現,而在抽象類別的子類別中實現接口中方法

使用規則:

  1. 抽象類別主要用於關係密切的對象,而介面最適合為不相關的類別提供通用功能。
  2. 如果要設計大的功能單元,則使用抽象類別;如果要設計小而簡練的功能區塊,則使用介面。
  3. 如果預計要建立元件的多個版本,則建立抽象類別。介面一旦創建就不能更改。如果需要介面的新版本,必須建立一個全新的介面。
  4. 如果建立的功能將在大範圍的全異物件間使用,則使用介面;如果要在元件的所有實作間提供通用的已實作功能,則使用抽象類別。
  5. 分析對象,提煉內部共性形成抽象類,用以表示對象本質,即「是什麼」。為外部提供呼叫或功能需要擴充時優先使用接口
  6. 好的介面定義應該是具有專一功能性的,而不是多功能的,否則造成介面污染。如果一個類別只是實作了這個介面的中一個功能,而不得不去實作介面中的其他方法,就叫介面污染
  7. 盡量避免使用繼承來實現組建功能,而是使用黑箱複用,即物件組合。因為繼承的層次增多,造成最直接的後果就是當你呼叫這個類別群中某一類,就必須把他們全部載入到堆疊中!後果可想而知。 (結合堆疊原理理解)。同時,有心的朋友可以留意到微軟在建構一個類別時,很多時候用到了物件組合的方法。例如 asp.net中,Page類,有Server Request等屬性,但其實他們都是某個類別的物件。使用Page類別的這個物件來呼叫另外的類別的方法和屬性,這個是非常基本的一個設計原則。

例如:

Window窗體可以用抽象類別來設計,可以把公有操作和屬性放到一個抽象類別裡,讓視窗和對話框繼承自這個抽象類,再根據自己的需求進行擴展和完善。

列印操作可以作為一個介面提供給每個需要此功能的視窗,因為視窗的內容不同,就要根據他們自己的要求去實現自己的列印功能。列印時只透過介面來調用,而不用在乎是那個視窗要印。

共通點、個性與選擇:

有的書上寫到C#推薦使用介面(Interface)來取代抽象基底類別(Abstract Class),並強調使用介面的諸多好處,這點我不敢苟同,從上面列表看來,兩者之間還是存在不少差異的,而這種差異的存在性必然決定了適用場景的不同,例如在抽象基類中可以為部分方法提供默認的實現,從而避免在子類中重複實現它們,提高代碼的可重用性,這就是抽象類別的優勢所在;而介面中只能包含抽象方法。至於何時使用抽象基底類別何時使用介面關鍵還是取決於使用者是如何看待繼承類別之間的聯繫的,使用者更加關心的是它們之間的個性差異還是它們之間的共通性聯繫。舉個生活中的例子加以說明。

如果給你三個物件分別是人、魚、青蛙,讓你為他們設計個基類來概括它們之間的聯繫,那麼首先給你的感覺肯定是它們個體間的差異性較大,很難抽象出共性,然而若讓你概括他們行為之間的共性,你可能想了想會意識到他們都會游泳,只不過是游泳方式迥異。那麼這時你就應該考慮使用介面而不是抽象基底類,原因有三條:

  1. 個性大於共通性。
  2. 差異較大的個性間具有某些相同的行為。
  3. 相同行為的實現方式有較大差異。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface ISwim
{
    void Swim();
}
 
public class Person : ISwim
{
    public void Swim()
    {
        //Swimming in person's style. 
    }
}
 
public class Frog : ISwim
{
    public void Swim()
    {
        //Swimming in frog's style. 
    }
}
 
public class Fish : ISwim
{
    public void Swim()
    {
        //Swimming in fish's style. 
    }
}

這時再給你三個對象,分別是鯽魚、鯉魚、金魚,仍然讓你設計基類來概括它們之間的聯繫,那麼你第一個意識到的肯定是它們都屬於魚類,其次是他們游泳的方式可能稍有差異,這時就應當使用抽象基類而不是介面,對比著上面的例子,原因也有三條:

  1. 共性大於個性
  2. 共性相同的個體間必然具有相同的屬性與行為
  3. 相同行為的實現方式有一定差別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
abstract public class Fish 
{ 
    abstract public void Swim(); 
}
 
public class 鲫鱼 : Fish 
{ 
    public override void Swim() 
    { 
        //Swim like a 鲫鱼 
    } 
}
 
public class 鲤鱼 : Fish 
{ 
    public override void Swim() 
    { 
        //Swim like a 鲤鱼 
    } 
}
 
public class 金鱼 : Fish 
{ 
    public override void Swim() 
    { 
        //Swim like a 金鱼 
    } 
}

觀察在使用介面或是使用抽象基底類別的幾個理由中,第三條理由其實是一樣的,它所描述的是物件導向中多態的概念,即透過覆蓋父類別的方法來實現,在運行時根據傳遞的物件引用,來呼叫對應的方法。第二個理由開始產生分歧,介面更強調了繼承物件間具有相同的行為,而抽象類別同時也強調了繼承物件間具有相同的屬性。而真正將介面與抽象基底類別區分開的則是理由歸納如下:

  • 當在差異較大的物件間尋求功能上的共通性時,使用介面。
  • 當在共性較多的物件間尋求功能上的差異時,使用抽象基底類別。

透過相同與不同的比較,我們只能說介面和抽象類,各有所長,但無優略。在實際的程式設計實踐中,我們要視具體情況來酌情量才,但是以下的經驗和積累,或許能給大家一些啟示,除了我的一些積累之外,很多都來自經典,我相信經得起考驗。所以在規則與場合中,我們學習這些經典,最重要的是學以致用,當然我將以一家之言博大家之笑,看官請繼續。

規則與場合:

  1. 請記住,物件導向思想的一個最重要的原則就是:面向介面程式設計。
  2. 借助介面和抽象類,23個設計模式中的許多想法被巧妙的實現了,我認為其精髓簡單說來就是:面向抽象程式設計。
  3. 抽象類別應主要用於關係密切的對象,而介面最適合為不相關的類別提供通用功能。
  4. 介面著重於CAN-DO關係型,而抽象類別則偏重於IS-A式的關係;
  5. 介面多定義物件的行為;抽象類別多定義物件的屬性;
  6. 介面定義可以使用public、protected、internal 和private修飾符,但是幾乎所有的介面都定義為public,原因就不必多說了。
  7. 「介面不變」,是應該考慮的重要因素。所以,在由介面增加擴展時,應該會增加新的接口,而不能更改現有介面。
  8. 盡量將介面設計成功能單一的功能塊,以.NET Framework為例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一個公共方法。
  9. 介面名稱前面的大寫字母「I」是約定,如同欄位名稱以下劃線開頭一樣,請堅持這些原則。
  10. 在介面中,所有的方法都預設為public
  11. 如果預計會出現版本問題,可以創建「抽象類別」。例如,創建了狗(Dog)、雞(Chicken)和鴨(Duck),那麼應該考慮抽象出動物(Animal)來應對以後可能出現風馬牛的事情。而在介面中新增成員則會強制要求修改所有衍生類,並重新編譯,所以版本式的問題最好以抽象類別來實作。
  12. 從抽象類別派生的非抽象類別必須包括繼承的所有抽象方法和抽象存取器的實作。
  13. 抽象類別不能使用new關鍵字,也不能被密封,原因是抽象類別不能被實體化。
  14. 在抽象方法宣告中不能使用 staticvirtual 修飾符。

Abstract

這兩個的確非常的像,主要都是為了實踐『多型』,但實際的用途並不一樣。

Introduction

interface和abstract class在語言層次的差異,我就不再贅述,本文主要是放在何時該使用interface?何時該使用abstract class?

interface用在當一個物件須和其他物件共同合作時,為了確保其他物件有我想要的method,所以定下interface要該物件遵守,在Design Pattern到處可以看到這種應用,如strategy,bridge,prototype…。

而abstract class是用在整個繼承體系的最上層,用來定義出整個繼承體系該有哪些method,子類別可以對這些method加以override,或維持和abstract class相同的功能。Design Pattern中的template method,factory method…等就是用這種手法。

或者更明白的說,我們知道在OO主要有兩種技術:繼承(Inheritance)和組合(Composition),而abstract class就是用在使用繼承技術時,而interface則是用在使用組合技術時。

使用繼承技術時,我們會將所有method由abstract class去宣告,然後由各子類別去override,若不得已某些class有自己的特殊method,則由該class自行宣告。

一旦使用組合時時,就牽涉到一個問題,你如何確保被你組合的物件有某個method呢?當你使用繼承時,因為所有的method都會被繼承,這不是問題,但組合就不一樣了,所以你必須建立一個interface,強迫要被你組合的物件,需實做這個interface,這樣當你要使用該物件時,才能確保有某個method可以呼叫。


Note

  • Interface 宣告越小越好,讓它可應用的地方越廣。所以Interface 的宣告都幾個Methods而已
  • Abstract Class 是越大越好,最好把全部都實作出來。子類別 只需要幾行Code就能做出很強大的功能
  • Interface 還有一強大的功能,就是降耦的功能。降低Class間的耦合性。輕易做到抽換元件的功能

一個類別只能繼承一個抽象類別,一個類別可以實現多個介面。

原創) interface和abstract class有何不同? (C/C++) (.NET) (C#)
c#中抽象类(abstract)和接口(interface)的相同点跟区别
【总结】abstract class抽象类与interface之间的区别

This post is licensed under CC BY 4.0 by the author.