淺談C# 多態的法力

前言:咱們都知道面向對象的三大特性:封裝,繼承,多態。封裝和繼承對於初學者而言比較好理解,但要理解多態,尤爲是深刻理解,初學者每每存在有不少困惑,爲何這樣就能夠?有時候感受很難以想象,由此,面向對象的魅力體現了出來,那就是多態,多態用的好,能夠提升程序的擴展性。經常使用的設計模式,好比簡單工廠設計模式,核心就是多態。編程

其實多態就是:容許將子類類型的指針賦值給父類類型的指針。也就是同一操做做用於不一樣的對象,能夠有不一樣的解釋,產生不一樣的執行結果。在運行時,能夠經過指向基類的指針,來調用實現派生類中的方法。若是這邊不理解能夠先放一放,先看下面的事例,看完以後再來理解這句話,就很容易懂了。
理解多態以前首先要對面向對象的里氏替換原則和開放封閉原則有所瞭解。設計模式

里氏替換原則(Liskov Substitution Principle):派生類(子類)對象可以替換其基類(超類)對象被使用。通俗一點的理解就是「子類是父類」,舉個例子,「男人是人,人不必定是男人」,當須要一個父類類型的對象的時候能夠給一個子類類型的對象;當須要一個子類類型對象的時候給一個父類類型對象是不能夠的!數組

開放封閉原則(Open Closed Principle):封裝變化、下降耦合,軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。所以,開放封閉原則主要體如今兩個方面:對擴展開放,意味着有新的需求或變化時,能夠對現有代碼進行擴展,以適應新的狀況。對修改封閉,意味着類一旦設計完成,就能夠獨立完成其工做,而不要對類進行任何修改。ide

對這兩個原則有必定了解以後就能更好的理解多態。函數

首先,咱們先來看下怎樣用虛方法實現多態

咱們都知道,喜鵲(Magpie)、老鷹(Eagle)、企鵝(Penguin)都是屬於鳥類,咱們能夠根據這三者的共有特性提取出鳥類(Bird)作爲父類,喜鵲喜歡吃蟲子,老鷹喜歡吃肉,企鵝喜歡吃魚。優化

建立基類Bird以下,添加一個虛方法Eat():ui

複製代碼
    /// <summary>
    /// 鳥類:父類
    /// </summary>
    public class Bird
    {
        /// <summary>
        /// 吃:虛方法
        /// </summary>
        public virtual void Eat()
        {
            Console.WriteLine("我是一隻小小鳥,我喜歡吃蟲子~");
        }
    }
複製代碼

建立子類Magpie以下,繼承父類Bird,重寫父類Bird中的虛方法Eat():this

複製代碼
    /// <summary>
    /// 喜鵲:子類
    /// </summary>
    public  class Magpie:Bird
    {
        /// <summary>
        /// 重寫父類中Eat方法
        /// </summary>
        public override void Eat()
        {
            Console.WriteLine("我是一隻喜鵲,我喜歡吃蟲子~");
        }
    }
複製代碼

建立一個子類Eagle以下,繼承父類Bird,重寫父類Bird中的虛方法Eat():spa

複製代碼
    /// <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;
    }
複製代碼

執行結果:

好了,就這麼方便,一個新的功能就擴展完畢了,我根本不須要查看源代碼是如何實現的,這就是多態的好處!

相關文章
相關標籤/搜索