C# 多態的實現

C# 多態的實現

封裝、繼承、多態,面向對象的三大特性,前兩項理解相對容易,但要理解多態,特別是深刻的瞭解,對於初學者而言可能就會有必定困難了。我一直認爲學習OO的最好方法就是結合實踐,封裝、繼承在實際工做中的應用隨處可見,但多態呢?也許未必,可能不經意間用到也不會把它跟「多態」這個詞對應起來。在此拋磚引玉,你們討論,我的能力有限,不足之處還請指正。
以前看到過相似的問題:若是面試時主考官要求你用一句話來描述多態,儘量的精煉,你會怎麼回答?固然答案有不少,每一個人的理解和表達不盡相同,但我比較趨向這樣描述:經過繼承實現的不一樣對象調用相同的方法,表現出不一樣的行爲,稱之爲多態。html

例1:java

代碼 
複製代碼
public class Animal
    {
        public virtual void Eat()
        {
            Console.WriteLine("Animal eat");
        }
    }

    public class Cat : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Cat eat");
        }
    }

    public class Dog : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Dog eat");
        }
    }

    class Tester
    {
        static void Main(string[] args)
        {
            Animal[] animals = new Animal[3];

            animals[0] = new Animal();
            animals[1] = new Cat();
            animals[2] = new Dog();

            for (int i = 0; i < 3; i++)
            {
                animals[i].Eat();
            }
        }
    }
複製代碼

輸出以下:程序員

Animal eat...面試

Cat eat...算法

Dog eat...編程

在上面的例子中,經過繼承,使得Animal對象數組中的不一樣的對象,在調用Eat()方法時,表現出了不一樣的行爲。c#

多態的實現看起來很簡單,要徹底理解及靈活的運用c#的多態機制,也不是一件容易的事,有不少須要注意的地方。設計模式

 

1. new的用法數組

先看下面的例子。微信

例2

 

代碼 
複製代碼
public class Animal
    {
        public virtual void Eat()
        {
            Console.WriteLine("Animal eat");
        }
    }

    public class Cat : Animal
    {
        public new void Eat()
        {
            Console.WriteLine("Cat eat");
        }
    }

    class Tester
    {
        static void Main(string[] args)
        {
            Animal a = new Animal();
            a.Eat();

            Animal ac = new Cat();
            ac.Eat();

            Cat c = new Cat();
            c.Eat();
        }
    }
複製代碼

運行結果爲:

Animal eat...

Animal eat...

Cat eat...

能夠看出,當派生類Cat的Eat()方法使用new修飾時,Cat的對象轉換爲Animal對象後,調用的是Animal類中的Eat()方法。其實能夠理解爲,使用new關鍵字後,使得Cat中的Eat()方法和Animal中的Eat()方法成爲絕不相關的兩個方法,只是它們的名字碰巧相同而已。因此, Animal類中的Eat()方法無論用仍是不用virtual修飾,也無論訪問權限如何,或者是沒有,都不會對Cat的Eat()方法產生什麼影響(只是由於使用了new關鍵字,若是Cat類沒用從Animal類繼承Eat()方法,編譯器會輸出警告)。

 

我想這是設計者有意這麼設計的,由於有時候咱們就是要達到這種效果。嚴格的說,不能說經過使用new來實現多態,只能說在某些特定的時候碰巧實現了多態的效果。

 

 

2.override實現多態

真正的多態使用override來實現的。回過去看前面的例1,在基類Animal中將方法Eat()用virtual標記爲虛擬方法,再在派生類Cat和Dog中用override對Eat()修飾,進行重寫,很簡單就實現了多態。須要注意的是,要對一個類中一個方法用override修飾,該類必須從父類中繼承了一個對應的用virtual修飾的虛擬方法,不然編譯器將報錯。

 

好像講得差很少了,還有一個問題,不知道你想沒有。就是多層繼承中又是怎樣實現多態的。好比類A是基類,有一個虛擬方法method()(virtual修飾),類B繼承自類A,並對method()進行重寫(override修飾),如今類C又繼承自類B,是否是能夠繼續對method()進行重寫,並實現多態呢?看下面的例子。

 

例3:

 

代碼 
複製代碼
public class Animal
    {
        public virtual void Eat()
        {
            Console.WriteLine("Animal eat");
        }
    }

    public class Dog : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Dog eat");
        }
    }

    public class WolfDog : Dog
    {
        public override void Eat()
        {
            Console.WriteLine("WolfDog eat");
        }
    }

    class Tester
    {
        static void Main(string[] args)
        {
            Animal[] animals = new Animal[3];

            animals[0] = new Animal();
            animals[1] = new Dog();
            animals[2] = new WolfDog();

            for (int i = 0; i < 3; i++)
            {
                animals[i].Eat();
            }
        }
}
複製代碼

 運行結果爲:

Animal eat...

Dog eat...

WolfDog eat... 

在上面的例子中類Dog繼承自類Animal,對方法Eat()進行了重寫,類WolfDog又繼承自Dog,再一次對Eat()方法進行了重寫,並很好地實現了多態。無論繼承了多少層,均可以在子類中對父類中已經重寫的方法繼續進行重寫,即若是父類方法用override修飾,若是子類繼承了該方法,也能夠用override修飾,多層繼承中的多態就是這樣實現的。要想終止這種重寫,只需重寫方法時用sealed關鍵字進行修飾便可。

 

 

3. abstract-override實現多態

先在咱們在來討論一下用abstract修飾的抽象方法。抽象方法只是對方法進行了定義,而沒有實現,若是一個類包含了抽象方法,那麼該類也必須用abstract聲明爲抽象類,一個抽象類是不能被實例化的。對於類中的抽象方法,能夠再其派生類中用override進行重寫,若是不重寫,其派生類也要被聲明爲抽象類。看下面的例子。

例4:

 

 

代碼 
複製代碼
public abstract class Animal
    {
      public abstract void Eat();
    }

    public class Cat : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Cat eat");
        }
    }

    public class Dog : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Dog eat");
        }
    }

    public class WolfDog : Dog
    {
        public override void Eat()
        {
            Console.WriteLine("Wolfdog eat");
        }
    }

    class Tester
    {
        static void Main(string[] args)
        {
            Animal[] animals = new Animal[3];

            animals[0] = new Cat();
            animals[1] = new Dog();
            animals[2] = new WolfDog();

            for (int i = 0; i < animals.Length; i++)
            {
                animals[i].Eat();
            }
        }
    }
複製代碼

運行結果爲:

Cat eat...

Dog eat...

Wolfdog eat...

從上面能夠看出,經過使用abstract-override能夠和virtual-override同樣地實現多態,包括多層繼承也是同樣的。不一樣之處在於,包含虛擬方法的類能夠被實例化,而包含抽象方法的類不能被實例化。

以上動圖由「圖鬥羅」提供

--------------------------------------------------------

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

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

里氏替換原則(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;
    }
複製代碼
複製代碼

執行結果:

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

-------------------------------------------------------------------------------------------------

多態性意味着有多重形式。在面向對象編程範式中,多態性每每表現爲"一個接口,多個功能"。

多態性能夠是靜態的或動態的。在靜態多態性中,函數的響應是在編譯時發生的。在動態多態性中,函數的響應是在運行時發生的。

-------------------------------------------------------------------------------------------------

何時用接口何時用抽象?

抽象:是對相同屬性和方法的提煉而得

接口:是對相同行爲不一樣實現方法的提煉

如:  每種支付方式 支付以前都須要校驗一下支付金額是否是真確的,不能小於等於0 。由於校驗方式,校驗代碼都是同樣的,因此咱們能夠定義一個 抽象類給抽象出來.

public abstract class AbstractPayWay implements PayWay{
	
	private Double money;
	
	private boolean verify(){
		return  money != null && money > 0; 
	}
	
	/**
	 * 這裏實現了  PayWay 中的  pay 接口 方法 因此 AbstractPayWay 的子類 無需 實現該 方法,
	 * 只須要 實現 doPay() 方法,而且 若是  doPay()方法被成功調用則說明確定 校驗成功了。
	 */
	@Override
	public boolean pay(){
		boolean verify = this.verify();
		if(!verify){
			System.out.println("支付金額驗證錯誤!");
			return false;
		}
		return this.doPay();
	}
	
	public abstract boolean doPay();
}

  因此  WeixinPayWay  ZhifubaoPayWay 支付的具體實現類能夠改改成

/**
 * 繼承  AbstractPayWay 因此無需 寫公共的校驗邏輯,直接寫支付業務邏輯
 * @author cyy
 */
public class WeixinPayWay extends AbstractPayWay{
 
    @Override
    public boolean doPay() {
        System.out.println("這裏無需校驗支付金額,直接調用支付方法就行");
        System.out.println("微信支付成功");
        return false;
    }
 
}
 
public class ZhifubaoPayWay extends AbstractPayWay{
 
 
    @Override
    public boolean doPay() {
        System.out.println("這裏無需校驗支付金額,直接調用支付方法就行");
        System.out.println("支付寶支付成功");
        return false;
    }
 
 
}

參考:

https://blog.csdn.net/wab719591157/article/details/73741919

https://blog.csdn.net/u012135077/article/details/48286837

https://blog.csdn.net/u013538542/article/details/45365019

轉:

java 中何時用抽象類,何時用 接口(面向對象的繼承與多態)

抽象類:強調的是把共同(共有、相同)的屬性方法, 抽象出來,統一寫在一個地方(他們的實現代碼是同樣的),方便維護。(面向對象三大特性中的繼承特性)

接口: 抽象的是行爲 - 同一種行爲的不一樣實現方式。當多個對象都擁有相同的行爲,可是行爲的具體實現方式不同的時候能夠用接口抽象(面向對象中的多態特性)

 

因此通常在實際項目中接口和抽象類是配合使用而不是相互替代

 

例如: 

全部的訂單都有單號,單價,數量。都擁有,並且相同,因此能夠用一個抽象類給統一描述出來。

public abstract class AbstractOrder {
 
    private String serialNo; // 單號
    
    private Double money;  // 單價
    
    private int number;  // 數量
}

再有一個商品訂單 還有一個獨有的 商品名稱 屬性。因此 在新新建一個 ProductOrder 繼承 AbstractOrder

public class ProductOrder extends AbstractOrder{
 
    private String productName;
}

另外 全部的訂單都須要支付,可是支付方式又不同好比,微信支付,支付寶支付,同一種行爲,可是具體的行爲方式又不同。因此用一個接口給抽象出來(規定一個行爲標準)

public interface PayWay {
 
    public boolean pay();
}
public class WeixinPayWay implements PayWay{
@Override
public boolean pay() {
System.out.println("微信支付成功");
return false;
}

}
public class ZhifubaoPayWay implements PayWay{
 
    @Override
    public boolean pay() {
        System.out.println("支付寶支付成功");
        return false;
    }
 
 
}

由於全部訂單都須要支付 因此 只須要 改造 AbstractOrder 類在裏面增長一個 支付行爲

public abstract class AbstractOrder {
 
    private String serialNo; // 單號
    
    private Double money;  // 單價
    
    private int number;  // 數量
    
    private PayWay payWay;  // 支付行爲
}

在好比  每種支付方式 支付以前都須要校驗一下支付金額是否是真確的,不能小於等於0 。由於校驗方式,校驗代碼都是同樣的,因此咱們能夠定義一個 抽象類給抽象出來

public abstract class AbstractPayWay implements PayWay{
    
    private Double money;
    
    private boolean verify(){
        return  money != null && money > 0; 
    }
    
    /**
     * 這裏實現了  PayWay 中的  pay 接口 方法 因此 AbstractPayWay 的子類 無需 實現該 方法,
     * 只須要 實現 doPay() 方法,而且 若是  doPay()方法被成功調用則說明確定 校驗成功了。
     */
    @Override
    public boolean pay(){
        boolean verify = this.verify();
        if(!verify){
            System.out.println("支付金額驗證錯誤!");
            return false;
        }
        return this.doPay();
    }
    
    public abstract boolean doPay();
}

因此  WeixinPayWay  ZhifubaoPayWay 支付的具體實現類能夠改改成

/**
 * 繼承  AbstractPayWay 因此無需 寫公共的校驗邏輯,直接寫支付業務邏輯
 * @author cyy
 */
public class WeixinPayWay extends AbstractPayWay{
 
    @Override
    public boolean doPay() {
        System.out.println("這裏無需校驗支付金額,直接調用支付方法就行");
        System.out.println("微信支付成功");
        return false;
    }
 
}
 
public class ZhifubaoPayWay extends AbstractPayWay{
 
 
    @Override
    public boolean doPay() {
        System.out.println("這裏無需校驗支付金額,直接調用支付方法就行");
        System.out.println("支付寶支付成功");
        return false;
    }
 
 
}

 

---------------------------------------------------------------------------------

問題:怎麼在開發中嘗試提煉抽象能力?

還得多 練習說設計模式和重構,固然在算法的基礎上

在此以前堅持三個原則

第一次用到某個功能時,你寫一個特定的解決方法;

第二次又用到的時候,你拷貝上一次的代碼;

第三次出現的時候,你才着手"抽象化",寫出通用的解決方法

 

從一個算命的段子談談抽象

這兩天有幸去參加了張逸老師(《架構之美》(評註版)以及《重構 : 改善既有代碼的設計》(評註版)的做者)的重構方面的培訓(公司組織),寫一篇文章分享一下這一兩天聽到的經典段子。

ps:這篇文章,可能沒啥乾貨。由於我不可能去講如何抽象,這種題材在公衆號上看,你們看的太累了。並且你們去買任何一本重構方面的書,估計都講的比我好。因此這篇文章,你們當段子看看便可。若是時間寶貴,慎重!能夠關閉,偶不會怪你的!

段子

基本上全部的文章講抽象舉例的時候,都是舉一些數學公式爲例子。所以數學公式就是從大量數據中抽象出來的一個定律。那今天要分享的就是一個算命的段子。

話說,某日,三個書生去算命,他們問:"老先生,看看咱們今年鄉試,誰可以高中?。"
老先生看了看他們的面相,只亮出了一根手指,就像下面這樣


不久,三個書生鄉試完畢,紛紛稱讚,老先生算的準。
那麼這是爲何呢?聰明的各位讀者想到了麼?

 

由於

  • 沒有書生考上,這個一根手指表明一個書生都沒考上

  • 一個書生考上,這個一根手指表明只有一個書生考上

  • 二個書生考上,這個一根手指表明只有一個書生沒考上

  • 三個書生考上,這個時候能夠這麼解釋,正所謂,道生,一輩子二,二生三,三生萬物。這個能夠理解爲圓滿,三個都考上,不是很圓滿麼。

這時候,有人不服,問要是有四個書生考試怎麼辦?OK,也能夠解釋

  • 沒有書生考上,這個一根手指表明一個書生都沒考上

  • 一個書生考上,這個一根手指表明只有一個書生考上

  • 二個書生考上,這個一根手指表明只有一對書生考上

  • 三個書生考上,這個一根手指表明只有一個書生沒考上

  • 四個書生考上,這個一根手指表明圓滿,你們都考上。

至於五個書生的狀況,你們自行推導吧。

OK,講完了。這個抽象就是從紛繁複雜的事物中提煉本質的過程。大家看,這個段子裏的就是對一切現象的抽象。那麼一個程序員的抽象能力,就是他的軟件設計能力。你們回憶一下本身作項目,從業務那邊接需求,而後轉化爲代碼,就是一個抽象的過程。你抽象的能力如何,就表明着你的代碼質量如何。
若是你的抽象能力很LOW,那你的代碼也很LOW,根本沒法下降問題的複雜度。縱使你作了n個CRUD的項目,你也沒法獲得能力的提升。

何時抽象

首先,不要瞎抽象,不少人,沒事瞎抽象,最後系統就抽筋了。並且,最開始系統需求不明確,需求變動頻繁,瞎抽象,不利於後期維護。推薦仍是邊寫邊抽象,一開始不要想太多。
具體原則,出自Martin Fowler在《Refactoring》中提出的三次原則

第一次用到某個功能時,你寫一個特定的解決方法;

第二次又用到的時候,你拷貝上一次的代碼;

第三次出現的時候,你才着手"抽象化",寫出通用的解決方法

理由:
(1)省事。若是一種功能只有一到兩個地方會用到,就不須要在"抽象化"上面耗費時間了。
(2)容易發現模式. 說句實在話,我拿到一個需求,我也沒法一下看出該用什麼設計模式。都是寫着寫着,發現該用什麼設計模式。因此邊寫邊抽象化,比較符合實際狀況。
(3)防止過分冗餘。若是一種功能同時有多個實現,管理起來很是麻煩,修改的時候須要修改多處。所以須要進行抽象化。

如何抽象

這塊就不細講了,你們隨便看幾本重構方面的書,都講的比我好。推薦《clean code》這本。
總的來講,遵循羅伯特·馬丁提出的五大原則 —— SOLID原則便可。

  • S Single Responsibility Principle - 單一職責原則

  • O Open Close Principle - 開閉原則

  • L Liskov Principle of Substitution - 裏式替換原則

  • I Interface Segregation Principle - 接口隔離原則

  • D Dependency Inversion Principle - 依賴倒置原則

這幾大原則,任何一本講設計模式的書基本都有提到,就不贅述了。你們有興趣能夠自行查閱。

總結

頻繁的需求變動會對軟件的生命週期形成嚴重的殺傷力。若是你的抽象能力不足,你的代碼就會愈加的臃腫,最後不得以進行二次開發。合理的抽象,能夠下降軟件代碼的複雜度,加強軟件的生命力,更是一個程序員編程能力和設計能力的體現。但願你們在這方面下足功夫。

相關文章
相關標籤/搜索