類別的多型,方法覆寫,方法多載,方法遮蔽介紹
訂閱 375
先決條件
閱讀本節的先決條件是對物件導向程式設計,類別,執行個體等概念有所掌握,你可以檢視物件導向程式設計,類別,執行個體介紹一節來了解他們。
類別的多型
類別的多型是指衍生類別可以改變調整繼承成員的實作,以符合自身的需求,這主要針對執行個體方法,或本質上是方法的執行個體成員。多型使得繼承與被繼承的物件,在同一功能上的表現可以不同。
變數型別與多型
在支援物件導向和宣告變數型別的語言中,變數的型別與值的資料型別可能不同,通過變數呼叫的方法,將指向物件對該方法的真實實作,當物件已經對方法做出調整或改變時。
在下面的 C# 程式碼中,雖然變數kitty
和animal
的型別都是Animal
,且Animal
類別的Bark
方法固定,但實際呼叫Bark
方法卻顯示了不同的資訊,因為變數kitty
對應的真實物件的型別是Kitty
,對於Bark
方法,Kitty
類別擁有自己的實作。
// 建立 Animal 和 Kitty 的執行個體
Animal animal = new Animal();
Animal kitty = new Kitty();
// 分別呼叫 Bark 方法,將顯示不同資訊
Console.WriteLine("animal.Bark");
animal.Bark();
Console.WriteLine("kitty.Bark");
kitty.Bark();
animal.Bark
試著發出聲音?
kitty.Bark
試著發出聲音?
喵喵喵!
方法覆寫
方法覆寫(Method Overriding)是實作多型的最簡單方式,一般的做法是在類別中再次定義需要覆寫的執行個體方法,此過程可能需要特定的關鍵字,比如,C# 中的override
。
什麽是虛擬方法?
虛擬方法是指允許被衍生類別覆寫的方法,通常對應關鍵字virtual
。在一些語言中,虛擬方法是不存在的,因為預設所有方法均可被覆寫。
由於虛擬方法允許被覆寫,所以他不應該具有任何封鎖該目標實作的特征,比如,私用的。
在衍生類別中呼叫基底類別的方法
在方法覆寫的過程中,你可能需要使用基底類別的方法,如果覆寫的目的在於補充功能。大部分語言會提供相關的關鍵字來完成此目標,比如,C# 中的base
關鍵字。
下面展示了之前範例中使用的類別Animal
和Kitty
,可以看到Animal
類別擁有一個虛擬方法Bark
,Kitty
類別對該方法進行了覆寫,並呼叫了基底類別的Bark
方法。
// 基底類別 Animal
class Animal
{
// 虛擬方法 Bark,嘗試發出聲音
public virtual void Bark()
{
Console.WriteLine("試著發出聲音?");
}
}
// 衍生類別 Kitty
class Kitty : Animal
{
// Kitty 覆寫了繼承自 Animal 的方法 Bark
public override void Bark()
{
// 呼叫基底類別的 Bark 方法
base.Bark();
Console.WriteLine("喵喵喵!");
}
}
什麽是抽象方法和抽象類別?
抽象方法是類別中已經宣告,但尚未具體實作的方法,他通常需要特定的關鍵字來標註,比如,C# 中的abstract
,並需要衍生類別來完成實作,一個方法被抽象的原因是其必要性和缺少通用性的邏輯。
由於需要通過衍生類別完成實作,抽象方法不能具有任何封鎖這一目標實作的特征,比如,私用的。
擁有抽象方法的類別被稱為抽象類別,因為沒有完全的具體實作,抽象類別無法被具現化。需要指出,抽象類別包括了,已繼承抽象方法但仍未對其實作的衍生類別。
抽象方法和虛擬方法之間的區別
抽象方法要求被具體實作,否則相關類別無法具現化,虛擬方法則表示允許被覆寫,能否具現化與是否被覆寫無關。
下面,為Animal
類別增加一個抽象方法Run
,Kitty
類別需要實作該方法後才能被具現化。
// 基底類別 Animal
abstract class Animal
{
// …
// 抽象方法 Run,奔跑起來
public abstract void Run();
}
// 衍生類別 Kitty
class Kitty : Animal
{
// …
// Kitty 必須實作抽象方法 Run,才能被具現化
public override void Run()
{
Console.WriteLine("你看,我跑起來了!");
}
}
// 建立 Kitty 的執行個體
Kitty kitty = new Kitty();
// 呼叫 Run 方法
kitty.Run();
你看,我跑起來了!
方法多載
方法多載(Method Overloading)與多型的關系不大,他允許一個類別定義多個名稱相同但簽章不同的方法,而不是將基底類別中的方法再次定義。方法多載在一些語言中是不被支援或不可行的,他們不允許一個類別擁有名稱相同的方法,方法簽章僅通過方法名稱產生,或者根本沒有簽章。
覆寫基底類別的多載方法需要比對方法簽章
這裏需要指出,在覆寫基底類別的多載方法時,請註意方法簽章是否相符。
選擇方法多載還是選擇性參數?
當方法內部的邏輯差異不大時,使用選擇性參數應該是更好,如果語言支援這一功能的話,你將不會看到一排排的方法定義,僅僅是為了多出或缺少的參數。
當不同參數組合帶來的邏輯差異較大時,可選擇使用方法多載,以使程式碼更容易維護。
函式
要了解方法簽章,你可以檢視函式簽章一段,好吧,名字不代表一切。
下面,通過多載為Kitty
類別新增兩個同名的Eat
方法,他們擁有的參數不同。
// 衍生類別 Kitty
class Kitty : Animal
{
// …
// 使用多載定義兩個同名的 Eat 方法
public void Eat(string something)
{
Console.WriteLine($"好耶,今天的午餐是:{something}");
}
public void Eat()
{
Console.WriteLine("今天吃空氣?!");
}
}
// 建立 Kitty 的執行個體
Kitty kitty = new Kitty();
// 呼叫多載的 Eat 方法
kitty.Eat("小魚幹");
kitty.Eat();
好耶,今天的午餐是:小魚幹
今天吃空氣?!
方法遮蔽
方法遮蔽(Method Hiding/Shadowing)和方法覆寫是比較容易混淆的,他們均針對基底類別的方法進行重新定義,但不同的是,通過遮蔽定義的方法斷絕了自身與基底類別方法的繼承關系,通過覆寫定義的方法則會保持。方法遮蔽和方法覆寫的選擇,將影響多型的表現效果。
在通過某種型別的變數呼叫方法時,需要確定實際物件是否存在對該方法的覆寫,而通過遮蔽定義的方法將打斷這一過程,先決條件是這些方法真實的阻斷了目前和之後的繼承關系,他們不能是私用的,或具有任何導致上述目標無法實作的特征。
在下面的 C# 程式碼中,變數a
,b
,c
,d
的型別均為A
,但變數b
對應的真實物件的型別為B
,類別B
覆寫了類別A
的Show
方法,因此b.Show()
指向類別B
對Show
方法的實作。變數c
對應的真實物件的型別為C
,類別C
遮蔽了類別B
的Show
方法,c.Show()
將不會指向類別C
對Show
方法的實作,因為類別C
的Show
方法與類別B
,A
的Show
方法不存在繼承關系。變數d
對應的真實物件的型別為D
,類別D
覆寫了類別C
的Show
方法,d.Show()
將不會指向類別D
,C
對Show
方法的實作,因為類別D
,C
的Show
方法與類別B
,A
的Show
方法不存在繼承關系。
// 所有變數的型別均為 A,但真實的物件型別分別為 A,B,C,D
A a = new A();
A b = new B();
A c = new C();
A d = new D();
// 分別呼叫他們的 Show 方法
a.Show();
b.Show();
c.Show();
d.Show();
呼叫了 A 的 Show 方法
呼叫了 B 的 Show 方法
呼叫了 B 的 Show 方法
呼叫了 B 的 Show 方法
// 類別 A
class A
{
// 虛擬方法 Show
public virtual void Show()
{
Console.WriteLine("呼叫了 A 的 Show 方法");
}
}
// 類別 B
class B : A
{
// 覆寫類別 A 的 Show 方法
public override void Show()
{
Console.WriteLine("呼叫了 B 的 Show 方法");
}
}
// 類別 C
class C : B
{
// 遮蔽類別 B 的 Show 方法,阻斷了繼承關系
public new virtual void Show()
{
Console.WriteLine("呼叫了 C 的 Show 方法");
}
}
// 類別 D
class D : C
{
// 覆寫類別 C 的 Show 方法
public override void Show()
{
Console.WriteLine("呼叫了 D 的 Show 方法");
}
}