抽象類

抽象類

通常在編程語句中用 abstract 修飾的類是抽象類。在C++中,含有純虛擬函數的類稱為抽象類,它不能生成對象;在java中,含有抽象方法的類稱為抽象類,同樣不能生成對象。

抽象類是不完整的,它只能用作基類。在面向對象方法中,抽象類主要用來進行類型隱藏和充當全局變數的角色。

基本概念


在面向對象的概念中,所有的對象都是通過類來描繪的,但是反過來,並不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。
抽象類往往用來表徵對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。
比如,在一個圖形編輯軟體的分析設計過程中,就會發現問題領域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域並不是直接存在的,它就是一個抽象概念。而正是因為抽象的概念在問題領域沒有對應的具體概念,所以用以表徵抽象概念的抽象類是不能夠實例化的。

特徵

抽象類具有以下特性:
• 抽象類不能實例化。
• 抽象類可以包含抽象方法和抽象訪問器。
• 不能用 sealed 修飾符修飾抽象類,因為這兩個修飾符的含義是相反的。採用 sealed 修飾符的類無法繼承,而 abstract 修飾符要求對類進行繼承。
• 從抽象類派生的非抽象類必須包括繼承的所有抽象方法和抽象訪問器的實際實現。

比較區別


具體類比較

1.抽象類不能直接實例化,並且對抽象類使用 new 運算符會導致編譯時錯誤。雖然一些變數和值在編譯時的類型可以是抽象的,但是這樣的變數和值必須或者為 null,或者含有對非抽象類的實例的引用(此非抽象類是從抽象類派生的)。
2.允許(但不要求)抽象類包含抽象成員。
3.抽象類不能被密封。

介面比較

抽象類表示該類中可能已經有一些方法的具體定義,但是介面就僅僅只能定義各個方法的界面(方法名,參數列表,返回類型),並不關心具體細節。
介面是引用類型的,和抽象類的相似之處有三點:
1.不能實例化;
2.包含未實現的方法聲明;
3.派生類必須實現未實現的方法,抽象類是抽象方法,介面則是所有成員(不僅是方法包括其他成員)。
抽象類與介面緊密相關。然而介面又比抽象類更抽象,這主要體現在它們的差別上:
1.類可以實現無限個介面,但僅能從一個抽象(或任何其他類型)類繼承,從抽象類派生的類仍可實現介面,從而得出介面是用來解決多重繼承問題的。
2.抽象類當中可以存在非抽象的方法,可介面不能,且它裡面的方法只是一個聲明必須用public來修飾沒有具體實現的方法。
3.抽象類中的成員變數可以被不同的修飾符來修飾,可介面中的成員變數默認的都是靜態常量(static final)。
4.抽象類是對象的抽象,然而介面是一種行為規範。
抽象類裡面可以有非抽象方法但介面里只能有抽象方法 聲明方法的存在而不去實現它的類被叫做抽像類(abstract class),它用於要創建一個體現某些基本行為的類,並為該類聲明方法,但不能在該類中實現該類的情況。不能創建abstract 類的實例。然而可以創建一個變數,其類型是一個抽像類,並讓它指向具體子類的一個實例。不能有抽像構造函數或抽像靜態方法。Abstract 類的子類為它們父類中的所有抽像方法提供實現,否則它們也是抽像類為。取而代之,在子類中實現該方法。知道其行為的其它類可以在類中實現這些方法。介面(interface)是抽像類的變體。在介面中,所有方法都是抽像的。多繼承性可通過實現這樣的介面而獲得。介面中的所有方法都是抽像的,沒有一個有程序體。介面只可以定義static final成員變數。介面的實現與子類相似,除了該實現類不能從介面定義中繼承行為。當類實現特殊介面時,它定義(即將程序體給予)所有這種介面的方法。然後,它可以在實現了該介面的類的任何對像上調用介面的方法。由於有抽像類,它允許使用介面名作為引用變數的類型。通常的動態聯編將生效。引用可以轉換到介面類型或從介面類型轉換,instanceof 運算符可以用來決定某對象的類是否實現了介面

運用要求


C++
標準c++沒有abstract關鍵字,代之使用純虛類實現類似的功能,詳見詞條“虛類”。
在實現介面時,常寫一個抽象類,來實現介面中的某些子類所需的通用方法,接著在編寫各個子類時,即可繼承該抽象類來使用,省去在每個都要實現通用的方法的困擾。
C#
1.抽象類應主要用於關係密切的對象,而介面最適合為不相關的類提供通用功能。
2.介面著重於CAN-DO關係類型,而抽象類則偏重於IS-A式的關係。
3.介面多定義對象的行為;抽象類多定義對象的屬性。
4.如果預計會出現版本問題,可以創建“抽象類”。例如,創建了狗(Dog)、雞(Chicken)和鴨(Duck),那麼應該考慮抽象出動物(Animal)來應對以後可能出現豬馬牛的事情。而向介面中添加新成員則會強制要求修改所有派生類,並重新編譯,所以版本式的問題最好以抽象類來實現。
5.從抽象類派生的非抽象類必須包括繼承的所有抽象方法和抽象訪問器的實現。
6.對抽象類不能使用new關鍵字,也不能被密封,原因是抽象類不能被實例化。
7.在抽象方法聲明中不能使用 static 或 virtual 修飾符。

Java

1.abstract class 在 Java 語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。但是,一個類卻可以實現多個interface。
2.在abstract class 中可以有自己的數據成員,也可以有非abstarct的成員方法,而在interface中,只能夠有靜態的不能被修改的數據成員(也就是必須是static final的,不過在 interface中一般不定義數據成員),所有的成員方法都是abstract的。
3.abstract class和interface所反映出的設計理念不同。其實abstract class表示的是"is-a"關係,interface表示的是"like-a"關係。
4.實現抽象類和介面的類必須實現其中的所有方法。抽象類中可以有非抽象方法。介面中則不能有實現方法。
5.介面中定義的變數默認是public static final 型,且必須給其初值,所以實現類中不能重新定義,也不能改變其值。
6.抽象類中的變數默認是 friendly 型,其值可以在子類中重新定義,也可以重新賦值。
7.介面中的方法默認都是 public,abstract 類型的。

運用實例


C++
為了讓一個類成為抽象類,至少必須有一個純虛函數。包含至少一個純虛函數的類視為抽象類。
純虛函數形式如下:
1virtualreturntypefunction()=0;
例如,類A有兩個純虛函數lock()、unlock()和一個虛析構函數:
1
2
3
4
5
6
7
classA
{
public:
virtualvoidlock(void)=0;
virtualvoidunlock(void)=0;
virtual~A(void);
}
將函數lock()和unlock()初始化為0使它們成為純虛函數,沒有0這個初使化器,它們僅僅是虛函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
classB:publicA
{
protected:
pthread_mutex_tx;
public:
B(void);
~B(void);
virtualvoidlock(void)
{
ead_mutex_lock(x);
}
virtualvoidunlock(void)
{
ead_mutex_unlock(x);
}
}
抽象類對於提供模式、藍圖和後代類遵循的原則有用,如果遵循了藍圖的語義,後代類的行為可能按抽象類提供者和使用者所期望的那樣。
通過使用抽象類,C++程序員可以提供C++組件的規範,在它的構建中指導組件的實現者。
C#
抽象類提供多個派生類共享基類的公共定義,它既可以提供抽象方法,也可以提供非抽象方法。如果派生類沒有實現所有的抽象方法,則該派生類也必須聲明為抽象類。另外,實現抽象方法由overriding方法來實現。
定義方法為:
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
29
30
31
32
33
34
35
36
37
38
///
///定義抽象類
///
abstractpublicclassAnimal
{
//定義靜態欄位
staticprotectedint_id;
//定義屬性
publicabstractstaticintId//在抽象方法聲明中不能使用static或virtual修飾符
{
get;
set;
}
//定義方法
publicabstractvoidEat();
//定義索引器
publicstringthis[inti]
{
get;//必須聲明主體,因為它未標記為abstract、extern或partial
set;
}
///
///實現抽象類
///
publicclassDog:Animal
{
publicstaticoverrideintId
{
get{return_id;}
set{_id=value;}
}
publicoverridevoidEat()
{
Console.Write("DogEats.")
}
}
Java
假設在問題領域中有一個關於Door的抽象概念,該Door具有執行兩個動作open和close,此時可以通過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:使用abstract class方式定義Door:
1
2
3
4
5
6
7
abstractclassDoor{
abstractvoidopen(); 
abstractvoidclose();
}
使用interface方式定義Door:
1
2
3
4
interfaceDoor{
voidopen();
voidclose();
}
其他具體的Door類型可以extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。就此看來,使用abstract class和interface沒有大的區別。
但如果需求要求Door還要具有報警的功能,就能得出差別。既然open、close和alarm屬於兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。
對於問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有報警的功能。說明它又能夠完成報警概念中定義的行為,所以報警概念可以通過interface方式定義。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
abstractclassDoor{
abstractvoidopen();
abstractvoidclose();
}
interfaceAlarm{
voidalarm();
}
classAlarmDoorextendsDoorimplementsAlarm{
voidopen(){…}
voidclose(){…}
voidalarm(){…}
}
這種實現方式基本上能夠明確的反映出對於問題領域的理解,正確的揭示設計意圖。

運用意義


在面向對象方法中,抽象類主要用來進行類型隱藏。構造出一個固定的一組行為的抽象描述,但是這組行為卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現為所有可能的派生類。模塊可以操作一個抽象體。由於模塊依賴於一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行為功能。為了能夠實現面向對象設計的一個最核心的原則OCP(Open-Closed Principle),抽象類是其中的關鍵所在。