明辨接口實現和虛函數重載的區別

Effective C# 原則20:Item 20: Distinguish Between Implementing Interfaces and Overriding Virtual Functions

粗略的看一下,感覺實現接口和虛函數重載是一樣的。你定義了一些對象,但是這些對象是在另一個類型裏申明的。你被第一感覺騙了,實現接口與虛函數重載是完全不同的。在接口裏定義的成員默認情況下,是根本不存在實際內容的。

派生類不能重載基類中的接口成員。接口可以隱式的實現,就是把它們從類的公共接口中隱藏。它們的概念是不同的而且使用也是不同的。

但你可以這樣的實現接口:讓你的派生類可以修改你的實現。你只用對派生類做一個Hook就行了。(譯註:相信寫過C++程序的人就知道hook是什麼意思,而且我也實在想不到把hook譯成什麼比較好,所以就直接用hook這個原詞了,就像bug一樣。)

爲了展示它們的不同之處,試着做一個簡單的接口以及在一個類中實現它:

 

interface IMsg
{
  
void Message();
}


public class MyClass : IMsg
{
  
public void Message()
  
{
    Console.WriteLine( 
"MyClass" );
  }

}


Message()方法是MyClass的公共接口,Message同樣可以用一個接口指針IMsg來訪問。現在讓我們來一點繁雜的,添加一個派生類:

 

public class MyDerivedClass : MyClass
{
  
public new void Message()
  
{
    Console.WriteLine( 
"MyDerivedClass" );
  }

}


注意到,我添加了一個關鍵字new在Message方法上,用於區別前面的一個Message(參見原則29)。MyClass.Message()不是虛函數,派生類可以不提供重載版本。MyDerived類創建了一個新的Message方法,但這個方法並不是重載MyClass.Message:它隱藏了原來的方法。而且,MyClass.Message還是可以通過IMsg的引用來訪問:

 

MyDerivedClass d = new MyDerivedClass( );
d.Message( ); 
// prints "MyDerivedClass".
IMsg m = d as IMsg;
m.Message( ); 
// prints "MyClass"

 

接口方法不是虛的,當你實現一個接口時,你就要在詳細的相關類型中申明具體的實現內容。

但你可能想要創建接口,在基類中實現這些接口而且在派生類中修改它們的行爲。這是可以辦法到的。你有兩個選擇,如果不訪問基類,你可以在派生類中重新實現這個接口:

 

public class MyDerivedClass : MyClass, IMsg
{
  
public new void Message()
  
{
    Console.WriteLine( 
"MyDerivedClass" );
  }

}

 

添加的IMsg讓你的派生類的行爲發生了改變,以至IMsg.Message現在是在派生類上使用的:

 

MyDerivedClass d = new MyDerivedClass( );
d.Message( ); 
// prints "MyDerivedClass".
IMsg m = d as IMsg;
m.Message( ); 
// prints "MyDerivedClass"

派生類上還是須要在MyDerivedClass.Message()方法上添加關鍵字new,這還是有一點隱患(參見原則29)。基類還是可以通過接口引用來訪問:

 

MyDerivedClass d = new MyDerivedClass( );
d.Message( ); 
// prints "MyDerivedClass".
IMsg m = d as IMsg;
m.Message( ); 
// prints "MyDerivedClass"
MyClass b = d;
b.Message( ); 
// prints "MyClass"

唯一可以修正這個問題的方法是修改基類,把接口的申明修改爲虛函數:

 

public class MyClass : IMsg
{
  
public virtual void Message()
  
{
    Console.WriteLine( 
"MyClass" );
  }

}


public class MyDerivedClass : MyClass
{
  
public override void Message()
  
{
    Console.WriteLine( 
"MyDerivedClass" );
  }

}


MyDerivedClass以及其它所有從MyClass派生的類可以申明它們自己的Message()方法。這個重載的版本每次都會調用:通過MyDerivedClass的引用,通過IMsg接口的引用,或者直接通過MyClass的引用。

如果你不喜歡混雜的虛函數概念,那就對MyClass的定義做一個小的修改:

 

public abstract class MyClass, IMsg
{
  
public abstract void Message();
}


是的,你可以用一個抽象方法來實現一個接口。通過申明一個接口內的抽象的方法,你可以讓你的所有派生都必須實現這個接口。現在,IMsg接口成爲了MyClass的一個組成部份,你的每一個派生類都必須實現它。

隱式接口實現,可以讓你在一個類上隱藏公共的接口成員方法,而且也實現了這個接口。它在實現接口和虛函數重載上繞了幾個圈。當有多個合適的函數版本時,你可以利用隱式接口的實現來限制用戶的編碼。在原則26講到的IComparable習慣會詳細的討論這一點。

實現接口讓我們有更多的選擇,用於創建和重載虛函數。你可以創建隱祕的實現,虛的實現,或者抽象關聯到派生類。你可以精確的決定,你的派生類如何以及何時,修改接口的默認實現。接口方法不是虛方法,而是一個獨立的約定!
===============================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章