介面(Interface). 抽象(Abstract). 虛擬(Virtual)的差異

這個問題記得面試時碰過兩. 三次,要你說出介面跟抽象類別兩者之間的差異,我只知道其中相似之處就是要讓子類別來實作其內容,其他也說不出來有什麼相異之處。直到最近工作常會接觸到介面,這個問題又被我想起來了。

在開始前先解說一下名詞,interfcae跟abstract類別都是在替子類別擴充一些功能。有些會有強制性,但有些沒有。強制有兩種做法,分別為:

1.abstract的強制行為稱為:override(覆寫)

2.interface的強制行為稱為:implement(實作)。另外補充,在java裡面,類別繼承是使用extent,介面繼承則是使用implement)

  • 抽象類別(abstract class):
    public abstract class Car
    {
        public abstract string Run();//abstract method一定要寫在abstract class裡面,class沒有加abstract的話會報錯
        public virtual string Navigation() { return "紙本地圖導航"; }
    }

AutoMobile繼承Car,由於Car類別有一個abstract method,所以AutoMobile一定要覆寫Run()。不管是abstract或是virtual,要覆寫時,都要加上override關鍵字

    public class AutoMobile: Car
    {
        public override string Run() 
        {
            return "時速可達100公里";
        }

        public override string Navigation()
        {
            //return base.Navigation();
            return "使用CarPlay 導航功能";
        }
    }

沒有覆寫Run()的話,可是會出現錯誤訊息的

virtual method則不像abstract method一樣,需要強制覆寫。由此可知抽象類別內的成員如果沒有abstract的關鍵字的話,是沒有強制性的!

    public class Bicycle: Car
    {
        public override string Run() 
        {
            return "時速可達30公里";
        }
    }

執行Program.cs看看結果

var autoMobile = new AutoMobile();
Console.WriteLine(autoMobile.Run());
Console.WriteLine(autoMobile.Navigation());

var bicycle = new Bicycle();
Console.WriteLine(bicycle.Run());
Console.WriteLine(bicycle.Navigation());

Console.ReadLine();

接著再增加一個MotorCycle類別,但Navigation方法不加上override關鍵字,來看看會出現什麼

    public class MotorCycle : Car
    {
        public override string Run() 
        {
            return "時速可達70公里";
        }

        public string Navigation()
        {
            return "使用手機GoogleMap導航功能";
        }
    }

Program.cs

這裡我們以多型的方式來建立一個Car物件,並宣告為MotorCycle型別(何謂多型可參考:[C#]多型(Polymorphism)一個簡單的範例)

省略...

Car motorCycle = new MotorCycle();//以多型宣告
Console.WriteLine(motorCycle.Run());
Console.WriteLine(motorCycle.Navigation());

Console.ReadLine();

結果如下,如果沒有加上override關鍵字的話,會執行父類別的Navigation(),因為motorCycle變數他就是一個Car物件,所以會執行父類別的方法。

我們把MotorCycle的Navigation加上override,再來看看執行結果

        public override string Navigation()
        {
            return "使用手機GoogleMap導航功能";
        }

加上override後就可以執行被覆寫的Navigation方法了

 

綜合以上結果,可以得知:

1.抽象(abstract)類別是客製化不同類別之間的共同項目,並由子類別自行覆寫,藉此子類別擴充功能。

2.抽象類別裡面的東西,不一定要強制複寫(但有加abstract關鍵字的就一定要強制覆寫)


  • 介面(Interface):

interface的方法無法實作,只提供interface成員定義。不像abstract class的method也可以有自己的實作內容。

這樣說可能有點抽象,我們先來建立一個interface。與Car類別不同的是,我們新增了一個Reverse()的倒車方法,並且讓MotorCycle類別來實作看看。

    public interface ICar
    {
        public string Run();
        public string Navigation();
        public string Reverse();//倒車
    }

interface不需要override關鍵字即可進行method的實作,所以我們把Run()的override拿掉。

    public class MotorCycle : ICar
    {
        public string Run() 
        {
            return "時速可達70公里";
        }

        public string Navigation()
        {
            return "使用手機GoogleMap導航功能";
        }
    }

這時會出現錯誤訊息,告訴我們沒有實作到Reverse這個方法。由此可以得知,與abstract類別不同的是,interface的所有的成員都是有強制性的,只要有繼承介面的話,都一定要實作其內容

接下來我們在來做個測試,如果MotorCycle類別同時繼承abstract跟interface的話,其成員覆寫(實作),會以誰為主?

先拿掉Navigation()來看看會發生什麼事。Navigation()在Car類別裡為virtual方法(不強制覆寫),但在ICar裡我們有定義一個Navigation(),但是剛剛有提到介面有強制性。在這樣的情形下會不會報錯呢?

    public class MotorCycle : Car, ICar
    {
        public override string Run()
        {
            return "時速可達70公里";
        }

        //public string Navigation()
        //{
        //    return "使用手機GoogleMap導航功能";
        //}

        public string Reverse()
        {
            return "後退";
        }
    }

結果是沒有任何錯誤訊息

這時的你可能會搞混,為什麼繼承了ICar,但有沒有實作Navigation,卻不會有任何錯誤訊息,我們來看以下的執行結果,就可以知道答案了。

原來是MotorCycle類別的父類別已經有Navigation()了,所以子類別繼承了父類別的Navigation方法,所以沒有出現MotorCycle未實作介面成員Navigation的錯誤訊息

綜合以上結果,可以得知:

1.介面先定義出不同子類別之間共有功能的定義。並由子類別自行實作,藉此擴充子類別功能。

2.介面與抽象類別不同,所有成員接必須強制進行實作

 

 abstract(抽象類別)interface(介面)
強制性不一定有強制性的,有加abstract的method必須要強制覆寫interface的實作有強制性
繼承子類別只能繼承一個抽象類別子類別可以繼承多個介面
實例不能被實例,專門用來被繼承不能直接實例,要透過子類別實例

 

什麼時候使用interface? 什麼時候使用abstract?
以我們上面的Car範例來說,車子共有的功能,如前進. 後退. 煞車我們可以使用interface來強制制定相關車子必有的功能。至於非必要功能則可以用abstract讓子類別自行來覆寫,例如坦克車一定有其他車子也有的前進. 後退. 煞車功能,但其他車子類別一定不會(?)有坦克的發射砲彈功能,所以我們就能把發射砲彈這個功能放到abstract讓需要此功能的子類別(如:坦克車. 裝甲車)來自行繼承覆寫。

以下為擷取別人文章的內容,也是在講述什麼時候使用interface,什麼時候該使用abstract。大家也可以參考看看喔!

若把焦點放在方法實作上,可能會感到有點迷思,因為兩者好像都可以被覆寫。筆者也找到一篇算淺顯易懂的文章(參4),該文作者以門為例,當我們在建立類別時,一般門可能會有開門跟關門,假如有天要增加一個新的功能,比如自動門、設定密碼,作法一可以直接加入abstract或interface中,但這樣會面臨到耦合度的問題。當要建立木門所有功能,明明木門不能設定密碼,但因為父層的interface或abstract有設定密碼的功能,導致木門也得到這個功能了,再把問題換成公司系統,不同職位的權限或可執行的功能一定不同,若出現這樣的狀況是非常不合理的事情。看完了他的例子應該會更有感覺,abstract class仍屬於class,故僅能單一繼承,而繼承interface則可以多重繼承,所以門為例,屬於名詞的共通特性,應該放在abstract,而屬於其他覆加的功能,則可以放在interface,透過interface可多重實現的特性,工程師就可以自行組合成各式各樣的門,那今天就介紹到這裡啦!

範例程式
 

ref:
0.如何在 C# 程式設計手冊 () 定義抽象屬性
1.什麼時候使用interface? 什麼時候使用abstract?
2.C#雜記 — 介面(interface)、抽象( abstract)、虛擬(virtual)之我見 | by 莊創偉 |
3.C# 繼承(Inheritance)(Y)