前言:我們都知道面向對象的三大特性:封裝,繼承,多態。封裝和繼承對於初學者而言比較好理解,但要理解多態,尤其是深入理解,初學者往往存在有很多困惑,為什麼這樣就可以?有時候感覺很不可思議,由此,面向對象的魅力體現了出來,那就是多態,多態用的好,可以提高程序的擴展性。常用的設計模式,比如簡單工廠設計模式,核心就是多態。
其實多態就是:允許將子類類型的指針賦值給父類類型的指針。也就是同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。如果這邊不理解可以先放一放,先看下面的事例,看完之後再來理解這句話,就很容易懂了。
理解多態之前首先要對面向對象的裡氏替換原則和開放封閉原則有所了解。
裡氏替換原則(Liskov Substitution Principle):派生類(子類)對象能夠替換其基類(超類)對象被使用。通俗一點的理解就是“子類是父類”,舉個例子,“男人是人,人不一定是男人”,當需要一個父類類型的對象的時候可以給一個子類類型的對象;當需要一個子類類型對象的時候給一個父類類型對象是不可以的!
開放封閉原則(Open Closed Principle):封裝變化、降低耦合,軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。因此,開放封閉原則主要體現在兩個方面:對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。對修改封閉,意味著類一旦設計完成,就可以獨立完成其工作,而不要對類進行任何修改。
對這兩個原則有一定了解之後就能更好的理解多態。
首先,我們先來看下怎樣用虛方法實現多態我們都知道,喜鵲(Magpie)、老鷹(Eagle)、企鵝(Penguin)都是屬於鳥類,我們可以根據這三者的共有特性提取出鳥類(Bird)做為父類,喜鵲喜歡吃蟲子,老鷹喜歡吃肉,企鵝喜歡吃魚。
創建基類Bird如下,添加一個虛方法Eat():
/// <summary> /// 鳥類:父類 /// </summary> public class Bird { /// <summary> /// 吃:虛方法 /// </summary> public virtual void Eat() { Console.WriteLine("我是一只小小鳥,我喜歡吃蟲子~"); } }
創建子類Magpie如下,繼承父類Bird,重寫父類Bird中的虛方法Eat():
/// <summary> /// 喜鵲:子類 /// </summary> public class Magpie:Bird { /// <summary> /// 重寫父類中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只喜鵲,我喜歡吃蟲子~"); } }
創建一個子類Eagle如下,繼承父類Bird,重寫父類Bird中的虛方法Eat():
/// <summary> /// 老鷹:子類 /// </summary> public class Eagle:Bird { /// <summary> /// 重寫父類中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只老鷹,我喜歡吃肉~"); } }
創建一個子類Penguin如下,繼承父類Bird,重寫父類Bird中的虛方法Eat():
/// <summary> /// 企鵝:子類 /// </summary> public class Penguin:Bird { /// <summary> /// 重寫父類中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只小企鵝,我喜歡吃魚~"); } }
到此,一個基類,三個子類已經創建完畢,接下來我們在主函數中來看下多態是怎樣體現的。
static void Main(string[] args) { //創建一個Bird基類數組,添加基類Bird對象,Magpie對象,Eagle對象,Penguin對象 Bird[] birds = { new Bird(), new Magpie(), new Eagle(), new Penguin() }; //遍歷一下birds數組 foreach (Bird bird in birds) { bird.Eat(); } Console.ReadKey(); }
運行結果:
由此可見,子類Magpie,Eagle,Penguin對象可以賦值給父類對象,也就是說父類類型指針可以指向子類類型對象,這裡體現了裡氏替換原則。
父類對象調用自己的Eat()方法,實際上顯示的是父類類型指針指向的子類類型對象重寫父類Eat後的方法。這就是多態。
多態的作用到底是什麼呢?
其實多態的作用就是把不同的子類對象都當作父類來看,可以屏蔽不同子類對象之間的差異,寫出通用的代碼,做出通用的編程,以適應需求的不斷變化。
以上程序也體現了開放封閉原則,如果後面的同事需要擴展我這個程序,還想再添加一個貓頭鷹(Owl),很容易,只需要添加一個Owl類文件,繼承Bird,重寫Eat()方法,添加給父類對象就可以了。至此,該程序的擴展性得到了提升,而又不需要查看源代碼是如何實現的就可以擴展新功能。這就是多態帶來的好處。
還是剛才的例子,我們發現Bird這個父類,我們根本不需要使用它創建的對象,它存在的意義就是供子類來繼承。所以我們可以用抽象類來優化它。
我們把Bird父類改成抽象類,Eat()方法改成抽象方法。代碼如下:
/// <summary> /// 鳥類:基類 /// </summary> public abstract class Bird { /// <summary> /// 吃:抽象方法 /// </summary> public abstract void Eat(); }
抽象類Bird內添加一個Eat()抽象方法,沒有方法體。也不能實例化。
其他類Magpie,Eagle,Penguin代碼不變,子類也是用override關鍵字來重寫父類中抽象方法。
Main主函數中Bird就不能創建對象了,代碼稍微修改如下:
static void Main(string[] args) { //創建一個Bird基類數組,添加 Magpie對象,Eagle對象,Penguin對象 Bird[] birds = { new Magpie(), new Eagle(), new Penguin() }; //遍歷一下birds數組 foreach (Bird bird in birds) { bird.Eat(); } Console.ReadKey(); }
執行結果:
由此可見,我們選擇使用虛方法實現多態還是抽象類抽象方法實現多態,取決於我們是否需要使用基類實例化的對象.
比如說 現在有一個Employee類作為基類,ProjectManager類繼承自Employee,這個時候我們就需要使用虛方法來實現多態了,因為我們要使用Employee創建的對象,這些對象就是普通員工對象。
再比如說 現在有一個Person類作為基類,Student,Teacher 類繼承Person,我們需要使用的是Student和Teacher創建的對象,根本不需要使用Person創建的對象,
所以在這裡Person完全可以寫成抽象類。
總而言之,是使用虛方法,或者抽象類抽象方法實現多態,視情況而定,什麼情況?以上我說的兩點~
接下來~~~~
我要問一個問題,喜鵲和老鷹都可以飛,這個飛的能力,我怎麼來實現呢?
XXX答:“在父類Bird中添加一個Fly方法不就好了~~”
我再問:“好的,照你說的,企鵝繼承父類Bird,但是不能企鵝不能飛啊,這樣在父類Bird中添加Fly方法是不是不合適呢?”
XXX答:“那就在能飛的鳥類中分別添加Fly方法不就可以了嗎?”
對,這樣是可以,功能完全可以實現,可是這樣違背了面向對象開放封閉原則,下次我要再擴展一個鳥類比如貓頭鷹(Owl),我還要去源代碼中看下Fly是怎麼實現的,然後在Owl中再次添加Fly方法,相同的功能,重復的代碼,這樣是不合理的,程序也不便於擴展;
其次,如果我還要添加一個飛機類(Plane),我繼承Bird父類,合適嗎?
很顯然,不合適!所以我們需要一種規則,那就是接口了,喜鵲,老鷹,飛機,我都實現這個接口,那就可以飛了,而企鵝我不實現這個接口,它就不能飛~~
好,接下來介紹一下接口如何實現多態~添加一個接口IFlyable,代碼如下:
/// <summary> /// 飛 接口 /// </summary> public interface IFlyable { void Fly(); }
喜鵲Magpie實現IFlyable接口,代碼如下:
/// <summary> /// 喜鵲:子類,實現IFlyable接口 /// </summary> public class Magpie:Bird,IFlyable { /// <summary> /// 重寫父類Bird中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只喜鵲,我喜歡吃蟲子~"); } /// <summary> /// 實現 IFlyable接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一只喜鵲,我可以飛哦~~"); } }
老鷹Eagle實現IFlyable接口,代碼如下:
/// <summary> /// 老鷹:子類實現飛接口 /// </summary> public class Eagle:Bird,IFlyable { /// <summary> /// 重寫父類Bird中Eat方法 /// </summary> public override void Eat() { Console.WriteLine("我是一只老鷹,我喜歡吃肉~"); } /// <summary> /// 實現 IFlyable接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一只老鷹,我可以飛哦~~"); } }
在Main主函數中,創建一個IFlyable接口數組,代碼實現如下:
static void Main(string[] args) { //創建一個IFlyable接口數組,添加 Magpie對象,Eagle對象 IFlyable[] flys = { new Magpie(), new Eagle() }; //遍歷一下flys數組 foreach (IFlyable fly in flys) { fly.Fly(); } Console.ReadKey(); }
執行結果:
由於企鵝Penguin沒有實現IFlyable接口,所以企鵝不能對象不能賦值給IFlyable接口對象,所以企鵝,不能飛~
好了,剛才我提到了飛機也能飛,繼承Bird不合適的問題,現在有了接口,這個問題也可以解決了。如下,我添加一個飛機Plane類,實現IFlyable接口,代碼如下:
/// <summary> /// 飛機類,實現IFlyable接口 /// </summary> public class Plane:IFlyable { /// <summary> /// 實現接口方法 /// </summary> public void Fly() { Console.WriteLine("我是一架飛機,我也能飛~~"); } }
在Main主函數中,接口IFlyable數組,添加Plane對象:
class Program { static void Main(string[] args) { //創建一個IFlyable接口數組,添加 Magpie對象,Eagle對象,Plane對象 IFlyable[] flys = { new Magpie(), new Eagle(), new Plane() }; //遍歷一下flys數組 foreach (IFlyable fly in flys) { fly.Fly(); } Console.ReadKey(); } }
執行結果:
由此,可以看出用接口實現多態程序的擴展性得到了大大提升,以後不管是再擴展一個蝴蝶(Butterfly),還是鳥人(Birder)創建一個類,實現這個接口,在主函數中添加該對象就可以了。
也不需要查看源代碼是如何實現的,體現了開放封閉原則!
接口充分體現了多態的魅力~~
以上通過一些小的事例,給大家介紹了面向對象中三種實現多態的方式,或許有人會問,在項目中怎麼使用多態呢?多態的魅力在項目中如何體現?
那麼接下來我做一個面向對象的簡單計算器,來Show一下多態在項目中使用吧!
加減乘除運算,我們可以根據共性提取出一個計算類,裡面包含兩個屬性 Number1和Number2,還有一個抽象方法Compute();代碼如下:
/// <summary> /// 計算父類 /// </summary> public abstract class Calculate { public int Number1 { get; set; } public int Number2 { get; set; } public abstract int Compute(); }
接下來,我們添加一個加法器,繼承計算Calculate父類:
/// <summary> /// 加法器 /// </summary> public class Addition : Calculate { /// <summary> /// 實現父類計算方法 /// </summary> /// <returns>加法計算結果</returns> public override int Compute() { return Number1 + Number2; } }
再添加一個減法器,繼承計算Calculate父類:
/// <summary> /// 減法器 /// </summary> public class Subtraction : Calculate { /// <summary> /// 實現父類計算方法 /// </summary> /// <returns>減法計算結果</returns> public override int Compute() { return Number1 - Number2; } }
在主窗體FormMain中,編寫計算事件btn_Compute_Click,代碼如下:
private void btn_Compute_Click(object sender, EventArgs e) { //獲取兩個參數 int number1 = Convert.ToInt32(this.txt_Number1.Text.Trim()); int number2 = Convert.ToInt32(this.txt_Number2.Text.Trim()); //獲取運算符 string operation = cbb_Operator.Text.Trim(); //通過運算符,返回父類類型 Calculate calculate = GetCalculateResult(operation); calculate.Number1 = number1; calculate.Number2 = number2; //利用多態,返回運算結果 string result = calculate.Compute().ToString(); this.lab_Result.Text = result; } /// <summary> /// 通過運算符,返回父類類型 /// </summary> /// <param name="operation"></param> /// <returns></returns> private Calculate GetCalculateResult(string operation) { Calculate calculate = null; switch (operation) { case "+": calculate = new Addition(); break; case "-": calculate = new Subtraction(); break; } return calculate; }
在該事件中主要調用GetCalculateResult方法,通過運算符,創建一個對應的加減乘除計算器子類,然後賦值給父類,其實這就是設計模式中的簡單工廠設計模式,我給你一個運算符你給我生產一個對應的加減乘除計算器子類,返回給我。。其實大多數的設計模式的核心就是多態,掌握好多態,設計模式看起來也很輕松。
現階段工作已經完成,但是過了一段時間,又添加新的需求了,我還要擴展一個乘法了,那好,很簡單只要創建一個乘法計算器繼承Calculate父類即可,看代碼:
/// <summary> /// 乘法計算器 /// </summary> public class Multiplication:Calculate { public override int Compute() { return Number1*Number2; } }
然後在GetCalculateResult函數中添加一個case 就好了:
switch (operation) { case "+": calculate = new Addition(); break; case "-": calculate = new Subtraction(); break; case "*": calculate = new Multiplication(); break; }
執行結果:
好了,就這麼方便,一個新的功能就擴展完畢了,我根本不需要查看源代碼是如何實現的,這就是多態的好處!