本文

1、抽象類別(Abstract)

  • 1、抽象類別不能實體化。
  • 2、抽象方法是用來給子類別重寫的方法,如果不重寫的話,抽象方法就沒有存在的意義。
  • 3、如果類別中包含抽象方法,那麼整個類別就必須定義為抽象類別,不論是否還包含其他一般方法。

那麼甚麼時候該用到抽象類別呢?
大話設計模式:「當實體化沒有任何意義的父類別,就可以考慮改成抽象類別。」
敘述的還是有點難理解,那我們用個例子來說明。 在前幾篇的內容我們都用到了特斯拉這個例子,我們今天就延用。

 	//產生一個車子的類別,定義了啟動以及消耗能源
        public abstract class 車子
        {
            public abstract string 消耗能源();

            public abstract string 啟動();
        }

        //產生一個特斯拉類別繼承車子
        public class 特斯拉 : 車子
        {
            public override string 啟動()
            {
                return "特斯拉啟動";
            }

            public override string 消耗能源()
            {
                return "消耗電能";
            }
        }

        //產生一個汽車類別繼承車子
        public class 汽車 : 車子
        {
            public override string 啟動()
            {
                return "汽車啟動";
            }

            public override string 消耗能源()
            {
                return "消耗汽油";
            }
        }
		
	private static void Main(string[] args)
        {
            汽車 car = new 汽車();
            Console.WriteLine(car.啟動());
            Console.WriteLine("汽車:" + car.消耗能源());

            特斯拉 Tesla = new 特斯拉();
            Console.WriteLine(Tesla.啟動());
            Console.WriteLine("特斯拉:" + Tesla.消耗能源());
        }


首先車子這個類別指定義了車子啟動方式、消耗能源;這是不管哪種車子都會具備的功能,因此我們把定義成一個抽象類別,來讓特斯拉以及汽車繼承後實作,今天如果是機車那我們也可以繼承車子這個抽象類別實作自己的啟動、以及消耗能源方式。

  • 抽象類別應該盡可能多擁有共用程式碼,盡可能減少擁有資料。

那甚麼後該運用抽象類別呢?

  • 抽象類別通常代表一個抽象概念,它提供一個繼承的出發點,當設計一個新的抽象類別時,一定是用來繼承的,所以,在一個以繼承關係形成的樹狀結構中,樹葉節點應該是具體類別,而樹枝節點均應該是抽象類別。

(圖片取自大話設計模式) 取自大話設計模式

2、介面(Interface)

  • 1、介面是把隱式公共方法和屬性組合起來,以封裝特定功能的一個集合。
  • 2、一旦類別實現了介面,類別就可以支援介面所指定的所有屬性和成員。
  • 3、宣告介面在語法上與抽象類別完全相同,但不允許提供介面中任何成員的執行方法。

接續上面的例子,我們今天多加了機車進來,所以現在程式變成

	//產生一個車子的類別,定義了啟動以及消耗能源。。
        public abstract class 車子
        {
            public abstract string 消耗能源();

            public abstract string 啟動();
        }

        //產生一個特斯拉類別繼承車子
        public class 特斯拉 : 車子
        {
            public override string 啟動()
            {
                return "特斯拉啟動";
            }

            public override string 消耗能源()
            {
                return "消耗電能";
            }
        }

        //產生一個汽車類別繼承車子
        public class 汽車 : 車子
        {
            public override string 啟動()
            {
                return "汽車啟動";
            }

            public override string 消耗能源()
            {
                return "消耗汽油";
            }
        }

        //機車類別繼承車子
        public class 機車 : 車子
        {
            public override string 啟動()
            {
                return "機車啟動";
            }

            public override string 消耗能源()
            {
                return "消耗汽油";
            }
        }
		
	private static void Main(string[] args)
        {
            汽車 car = new 汽車();
            Console.WriteLine(car.啟動());
            Console.WriteLine("汽車:" + car.消耗能源());

            特斯拉 Tesla = new 特斯拉();
            Console.WriteLine(Tesla.啟動());
            Console.WriteLine("特斯拉:" + Tesla.消耗能源());

            機車 bike = new 機車();
            Console.WriteLine(bike.啟動());
            Console.WriteLine("機車:" + bike.消耗能源());
        }

今天我們想替車子加一個開車門的方法,但機車並不需要,這時候介面就可以派上用場了。

	//宣告一個車門的介面
	//這邊請注意因為示範所以使用"車門",一般介面規範使用I開頭,所以車門介面通常會宣告成Idoor
        public interface 車門
        {
            public string 打開方式();
        }

接下來讓汽車跟特斯拉實作車門這個介面,程式就變成

	//產生一個車子的類別,定義了啟動以及消耗能源。
        public abstract class 車子
        {
            public abstract string 消耗能源();

            public abstract string 啟動();
        }

        //產生一個特斯拉類別繼承車子、車門介面
        public class 特斯拉 : 車子, 車門
        {
            public override string 啟動()
            {
                return "特斯拉啟動";
            }

            public override string 消耗能源()
            {
                return "消耗電能";
            }

            public string 打開方式()
            {
                return "側開";
            }
        }

        //產生一個汽車類別繼承車子、車門介面
        public class 汽車 : 車子, 車門
        {
            public override string 啟動()
            {
                return "汽車啟動";
            }

            public override string 消耗能源()
            {
                return "消耗汽油";
            }

            public string 打開方式()
            {
                return "正開";
            }
        }

        //機車類別繼承車子
        public class 機車 : 車子
        {
            public override string 啟動()
            {
                return "機車啟動";
            }

            public override string 消耗能源()
            {
                return "消耗汽油";
            }
        }
		
	private static void Main(string[] args)
        {
            汽車 car = new 汽車();
            Console.WriteLine(car.啟動());
            Console.WriteLine("汽車:" + car.消耗能源());
            Console.WriteLine("汽車車門打開方式:" + car.打開方式());

            特斯拉 Tesla = new 特斯拉();
            Console.WriteLine(Tesla.啟動());
            Console.WriteLine("特斯拉:" + Tesla.消耗能源());
            Console.WriteLine("特斯拉車門打開方式:" + Tesla.打開方式());

            機車 bike = new 機車();
            Console.WriteLine(bike.啟動());
            Console.WriteLine("機車:" + bike.消耗能源());
        }

這樣一來機車並沒有車門並不需要有打開車門的方法,而汽車與特斯拉有車門,就可以透過介面實作打開車門的方法。

抽象類別與介面區分方法

型態上區別:

  • 1、抽像類別可以給出一些成員的實現,介面卻不包含成員的實現。
  • 2、抽象類別可被子類別實現,介面的成員需要類別完全實現。
  • 3、一個類別只能繼承一個抽象類別,但可實現多個介面。

三點方式更能夠區分:

一、類別是對物件的抽象;抽象類別是對類別的抽象;介面是對行為的抽象。

介面是對類別局部(行為)的抽像,而抽象類別是對類別整體(欄位、屬性、方法)的抽象。

二、如果行為跨越不同類別的物件,可使用介面;對於一些相似的類別物件,用繼承抽象類別。

這裡舉例以前上課老師喜歡舉的例子:
超人也是人他可以繼承人,但超人會飛;人不會飛,所以超人可以先繼承人,在使用介面實作飛行的行為。
超人與人都屬於人這個物件,但飛這個行為跨越了人與超人之間的關連性,所以使用介面來實作。
(看了大話設計後發現老師也是抄來的?)

三、從設計角度來講,抽象類別是從子類別中發現了公共的東西,泛化出了父類別,然後子類別再來繼承父類別;而介面是根本不知道子類別的存在,方法如何實現還不清楚,就預先定義了。

從前面超人與人的例子,不管是男人還是女人亦或是超人,都有同四肢、五官,所以發現了公共的東西,泛化出人這個父類別;而飛行這個介面不管是套用在超人飛、小鳥飛、飛機飛,飛行這個介面並不知道誰會使用它就預先被定義出來。

後記

剛進公司最不解的就是為何要使用介面,透過一段時間的使用也開始漸漸有所感觸,現在回頭看感想大概就像大話設計模式給出的結語一樣:「只有真正充分理解設計模式,那麼你才能算是真正會靈活使用抽象別和物件了。」
前方路還長啊…

參考連結