這個做法延伸做法 14 的內容,主要在討論 Abstract Method、Virtual Method 與 Interface 之間的差異。
下面是一個常見的抽象類別包含了一個 abstract 方法與 virtual 方法,它們的差異也很明顯,abstract 方法不需要提供實做但是強制要求 衍生類別必須提供實做細節,virtual 方法則是提供實做細節但不強制衍生類別提供新的細節。
如果你在 abstract 方法加上大括號那編譯器會馬上提示錯誤,同樣道理如果在 virtual 方法不加上大括號也會把上提示錯誤。
void Main()
{
var m = new MyClass();
}
public abstract class MyBaseClass
{
public abstract int Add();
public virtual int Add1()
{
return 1;
}
}
public class MyClass : MyBaseClass
{
public override int Add()
{
throw new NotImplementedException();
}
}
這裡直接把 abstract class
改成 interface
會發現竟然也可以定義 abstract 方法與 virtual 方法。
void Main()
{
var m = new MyClass();
}
public interface MyInterface
{
public abstract int Add();
public virtual int Add1()
{
return 1;
}
}
public class MyClass : MyInterface
{
public int Add()
{
throw new NotImplementedException();
}
}
這段程式碼可以看出 Abstract Method 與 Interface 確實有相似之處,都是在另一個型別提供定義然後在新的型別繼承或實做細節。
public class C
{
public void M()
{
MyClass myClass = new MyClass();
}
}
public interface MyInterface
{
int Add();
int Add1()
{
return 1;
}
}
public class MyClass : MyInterface
{
public int Add()
{
throw new NotImplementedException();
}
}
接下來看看它們之間的差異,首先下面這段程式碼建立了一個 IMessage
介面與 MyClass
類別,由於 MyClass
有實做 IMessage
介面,
所以我們可以透過 IMessage
介面來呼叫 Message
方法。
void Main()
{
MyClass b = new MyClass();
b.Message(); // prints "MyClass".
IMessage m = b as IMessage;
m.Message(); // prints "MyClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
public void Message() => Console.WriteLine(nameof(MyClass));
}
接下來新增 MyDerivedClass
衍生類別,注意到 MyClass.Message
方法並不是 virtual 所以不能透過 override 覆寫 Message
方法,
所以衍生類別使用 new
關鍵字建立新的方法並把舊的方法隱藏起來,這裡只是隱藏並沒有覆寫的操作發生,所以我們還是可以透過介面 IMessage
呼叫 Message
方法。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
public void Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass
{
public new void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
你可能會覺得 MyDerivedClass
是 MyClass
的衍生類別,那 MyDerivedClass
應該也有實做 IMessage
介面才對,但為什麼
呼叫 m.Message()
的時候是回傳 MyClass 而不是 MyDerivedClass?
是因為介面有兩種實做方式分別是 explicit implementation
與 implicit implementation
,其中 explicit implementation
是比較使用到的,
下面可以把 MyClass
改成明確實做的方式,使用這種方式目的是為了要確保方法只能透過公開的介面來呼叫,所以在 MyClass
實做這個方法的時候
必須宣告為 private,以免有人從 MyClass
呼叫方法。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
void IMessage.Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass
{
public void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
你會發現沒辦法把 MyDerivedClass
類別改成明確實做的方式,因為它並沒有在他的類別明確實做 IMessage
介面,所以在 MyDerivedClass
類別
確實找不到實做 IMessage 的 Message 方法,最後只能到基底類別尋找並呼叫。
所以只要在 MyDerivedClass
類別明確實做 IMessage
就能改變執行的結果,這樣就能做到在基底類別提供預設實做,
等待未來有需要可以在衍生類別修改實做細節的效果。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyDerivedClass"
}
interface IMessage
{
public void Message();
}
public class MyClass : IMessage
{
void IMessage.Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass, IMessage
{
public void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
不過這裡還是使用 new 建立新方法,所以我們還是能透過 MyClass
呼叫隱藏的 Message
方法,
這個現象在以前的文章 Effective-CSharp-10 有討論過。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyDerivedClass"
MyClass b = d;
b.Message(); // prints "MyClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
public void Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass, IMessage
{
public new void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
要處理的話可以透過修改基底類別的方法並將它宣告為 virtual,之後衍生類別使用 override 進行覆寫。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyDerivedClass"
MyClass b = d;
b.Message(); // prints "MyDerivedClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
public virtual void Message() => Console.WriteLine(nameof(MyClass));
}
public class MyDerivedClass : MyClass, IMessage
{
public override void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
你也可以改用建立抽象類別與方法達到同樣的效果,在 MyClass 中雖然有實做介面但是沒有提供細節,反而是將實做細節的動作推遲到衍生類別, 這段寫法展現出實做介面並不需要完整細節。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyDerivedClass"
MyClass b = d;
b.Message(); // prints "MyDerivedClass"
}
interface IMessage
{
void Message();
}
public abstract class MyClass : IMessage
{
public abstract void Message();
}
public class MyDerivedClass : MyClass, IMessage
{
public override void Message() => Console.WriteLine(nameof(MyDerivedClass));
}
也可以在基底類別實做介面細節,但是透過另一個內部的虛擬方法同樣能夠把運行細節往後推遲。
void Main()
{
MyDerivedClass d = new MyDerivedClass();
d.Message(); // prints "MyDerivedClass".
IMessage m = d as IMessage;
m.Message(); // prints "MyDerivedClass"
MyClass b = d;
b.Message(); // prints "MyDerivedClass"
}
interface IMessage
{
void Message();
}
public class MyClass : IMessage
{
protected virtual void OnMessage() {}
public void Message()
{
OnMessage();
}
}
public class MyDerivedClass : MyClass, IMessage
{
protected override void OnMessage() => Console.WriteLine(nameof(MyDerivedClass));
}
Summary
實做介面與 abstract virtual 方法相比彈性更高,因為實做介面的時候可以為 virtual 或者是 abstract, 或者是像最後一個寫法另外提供 virtual 方法給之後衍生類別實做細節。