類別的多型,方法覆寫,方法多載,方法遮蔽介紹
訂閱 480
先決條件
閱讀本節的先決條件是對物件導向程式設計,類別,執行個體等概念有所掌握,你可以檢視物件導向程式設計,類別,執行個體介紹一節來了解他們。
類別的多型
類別的多型是指衍生類別可以改變調整繼承成員的實作,以符合自身的需求,這主要針對執行個體方法,或本質上是方法的執行個體成員。多型使得繼承與被繼承的物件,在同一功能上的表現可以不同。
變數型別與多型
在支援物件導向和宣告變數型別的語言中,變數的型別與值的資料型別可能不同,通過變數呼叫的方法,將指向物件對該方法的真實實作,當物件已經對方法做出調整或改變時。
在下面的 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 方法");
}
}