延續著上一篇所著重的是在於介面(Interface)與抽象類別(Abstract Class)的概念說明,相信讀者對於這兩者的差異有了初步的瞭解,但對於物件導向的初學者來說,或許還是充滿著疑惑,所以我將使用範例及簡單的實作來加深印象。
在此之前我為各位整理一下在.NET中的介面與抽象類別兩者在實作時的相同及相異之處:
相同:
- 兩者都不能被具體化。
- 都必須去實作已宣告的『抽象』方法。
相異:
- 一個類別能實作很多介面,但只能繼承一個抽象類別。
- 介面所定義的每個方法都必須在延伸類別中實作,抽象類別則可以定義部分的方法是已經具有此方法的內容描述,而部分是定義必須在延伸類別中去實作的。這麼說或許讀者會與我早先說明的介面與抽象類別『都必須去實作已宣告抽象的方法』感到疑惑,這部分會在稍後做說明。
- 介面強調的是定義一些功能給較不相干的類別使用,抽象類別主要是應用在關係密切(is a)的類別中使用。
- 抽象類別一定屬於繼承架構,而且一定是父類別,介面可以讓毫無關係的類別來實作同一個介面,這點同時也呼應了第三點的說明。
說到這裡我們先從程式語法上來看介面與抽象類別。
介面(Interface)
interface InterFace1
{
string Method1();
int Method2();
bool Method3();
}
抽象類別(Abstract Class)
public abstract class AbstractClass
{
public abstract string Method1();
public abstract int Method2();
public bool Method3(){
bool a = false;
return false;
}
}
各位應該可以看出其實兩者是很類似的,因為他們都包含了必須被客戶端類別程式實作的『抽象成員函式』,不過在介面中,每個成員函式都是需要被他的客戶端類別程式所實作的,而抽象類別中則可包含了一個不一定要客戶端類別程式實作的成員函數(非抽象成員函式),如這裡所看到的 Method3(),他已經有實作了該成員函式所需要的敘述內容也就是說可以預設給給他行為,這就是兩者不同點之一。不過若你把抽象類別的 Method3() 敘述內容拿掉,並在前面加上 abstract 的話,你會發現其實他跟介面是很類似的,若歸納來說其實介面可算是抽象類別的特例(special-case)。
由架構設計的觀念來說,使用抽象類別一定與繼承有關係,但一個客戶端類別程式只能存在一個繼承關係,然而一個客戶端的類別程式卻可以實作多個不同的介面,因此經常有人使用介面來解決多重繼承的問題。
再從另一個架構設計的角度來看,因為介面不能定義一些成員函式預設的行為,因此,若你恰好要設計給每一個實作的客戶端類別程式都有預設『共同』的行為時,此時若使用介面的話,你就必須一一的為實作過此介面的客戶端類別程式來新增同樣的程式內容,這是一件多麼費工的事情阿!而且不小心還會 Copy & Paste 錯了,相同功能的程式碼也會重複存放而不易維護呢!
若使用抽象類別時,你便可以應用他的特性,只要在客戶端類別程式的父類別(抽象類別),新增一個成員函式的預設行為便可。所以,在此之前你就必需考慮到所欲達到的功能是否有上述新增共用成員函式的必要性的必要性,才能審慎選擇到底是要用介面或者是抽象類別。這也說明了我所提到過的,在設計介面時要考慮到將來實作此介面的客戶端類別程式與介面本身之間是否具有高度的相關性,白話一點就是說你很難去解釋實作介面的客戶端類別程式是某個介面,這樣子的一個關係,如實作 .Net Framework 內建的 ICollecton 的客戶端類別程式時,你不能說他就是屬於 ICollection 這樣的本質關係,你只能說他『像是』一個ICollection,但是相反的若你可以解釋客戶端類別程式是某個類別時,就盡量修改成抽象類別來實作此功能,因為他們具有密切的相關性,在未來你想要新增共同的預設行為時,便不會干擾到使用了相同的抽象類別的『不同本質』之客戶端類別程式,而且可以避免上述的問題發生。
或許你還是存在很多的懷疑,所以接下來我拿網路上常看的的例子來為大家做更深入的陳述。現在我們考慮一個關於 Door 的概念,這個 Door 具有兩個行為方法分別是 Open() 與 Close(),我分別用介面與抽象類別來實作,
介面
interface InterFace1
{
object Open();
object Close();
}
抽象類別
public abstract class Door
{
public abstract object Open();
public abstract object Close();
}
以上介面與抽象類別其實是具備類似的功能。但現在問題來了,房子的主人要求你要增加警報的功能,若你是設計師我想你會很直覺的加上Alert()成員函式如下:
介面interface InterFace1
{
object Open();
object Close();
object Alert();
}
抽象類別
public abstract class Door
{
public abstract object Open();
public abstract object Close();
public abstract object Alert();
}
客戶端類別程式如下:
介面
public class AlarmDoor : InterFace1
{
object Open(){}
object Close(){}
object Alert(){}
}
抽象類別public class AlarmDoor : Door
{
public override object Open(){}
public override object Close(){}
public override object Alert(){}
}
但其實這種作法違反了物件導向的介面隔離原則(ISP),因為 Clients should not be forced to depend upon interfaces that they do not use,也就是說 Door 既有的功能Open() 及 Close() 其實是不會有太多改變的,且這樣的行為方法與 Alert() 在概念上來說是不應該混在一起的,因為若有些客戶端的類別程式是衍生或實作至 Door 時,若此時修改了Alert()的行為方法時,將影響到所有客戶端類別程式,因此盡量避免這樣的設計,以免除掉相互依賴性。
不過上面的敘述反映出兩個問題:
- 我們可能沒有理解清楚問題領域,AlarmDoor 在概念本質上到底是 Door 還是報警器?
- 如果我們對於問題領域的理解沒有問題,如:我們對於問題領域的分析發現AlarmDoor 在概念的本質上和 Door 是一致的,那麼我們在實作時就不能正確的揭示我們的設計意圖,因為在這兩個概念的定義上反映不出上述含義。
其實就 AlarmDoor 而言在本質上他是一個 Door 所以他可以是一個抽象類別,但 AlarmDoor 又有警報功能,就好像他可以完成一件警報工作,所以可以利用介面的方式來定義他。
public abstract class Door
{
public abstract object Open();
public abstract object Close();
}
public interface Alarm
{
object Alert();
}
public class AlarmDoor1 : Door, Alarm
{
public override object Open(){}
public override object Close(){}
public object Alert(){}
}
結論:
其實介面與抽象類別在設計時有個原則就是,介面隱含著 (like a) 的關係,而抽象類別存在著 (is a) 的關係,因此若你對於你要解決的問題本身有比較深入的瞭解後,不難分析出這兩者應用時的觀點,因此,只要多去思考問題本身的意涵後相信你應該可以迎刃而解,不再有疑惑了。
參考資料:
http://ccl.cis.nctu.edu.tw/class/ooad/MartinPrincipleSummary.htm
http://www.cnblogs.com/kavenmo/archive/2004/10/05/49124.html
2008/6/26 20:59|
閱讀數 : 296
|
我要推薦
|
|
文章分類:
理論基礎
訂閱