# 抽象類別與抽象方法 & 介面(interface) ## 前瞻知識: #### 方法 method: ```python class Power :// 此稱為一個類別 def computer (): // 此稱為一個方法 ... def phone () // 這也叫一個方法 ... ``` #### 功能 function: ```python def computer: // 此稱為一個功能 function ... def phone: // 功能function ... ``` 所以如果當一個function(功能) 被 class 包起來的時候(被涵蓋在裡面),此時我們就將function(功能)稱為 method 方法。 # 抽象方法 & 抽象類別 (abstraction method & abstraction class): 先說教科書上的定義: ### 抽象方法(abstraction method): > 只有定義方法(成員函數)的「呼叫方式」,而沒有定義方法實際上到底要做什麼事情的程式碼。也就是說,抽象方法只有方法的頭(名稱和小括號()區塊),而沒有方法的身體(沒有大括號{ }區塊)。 ```java abstract class Animal { // 抽象類別 class bird (); // 這邊沒有身體(也就是沒有方法 method 的實作) } ``` 特別注意的是,抽象方法只能定義在抽象類別的{ }之中,否則會編譯錯誤。 ```java abstract class Animal { class bird () { ... } // <- error } ``` Abstract method 則是一種只有宣告,而沒有實作內容的一種特殊 method。Abstract method 主要的目標是預先宣告 method,但是把實作的工作交給繼承的類別來做。透過使用 abstract method,就可以讓衍生類別具有共同行為,但是實際的做法則由各個類別自訂。 抽象類別(abstraction class): 類別裡面包含了「至少一個」抽象方法,就稱為抽象類別。如果一個類別中包含了至少一個抽象方法,則該類別就必須被宣告成「抽象類別」(加上abstract關鍵字)例如: 「public abstract class Animal{ … … … }」。 這樣的定義讀者應該還是看不太懂,以實例來說明。如果有一個表格,上面寫著「姓名:______」,你就明確知道這裡要填上你的名字。「姓名:______」就類似於「抽象方法」,因為它只定義了呼叫方式,但沒有實際可作用的程式碼。 而「抽象類別」就是指裡面含有「至少一個」抽象方法的類別,例如機場海關的入境表格就是一個抽象類別,表格的格式雖然都有清楚的標示和規範,但實際的內容全部都是空白的,要旅客自己填寫。 入境表格「強迫」每個旅客一定要複寫(override)掉抽象方法,也就是要把入境表格的抽象方法給填上每個旅客自己的資料,表格才會發揮效用。而旅客填表的動作,就稱為「實作」(implement),也就是字面上的意思:把實際的內容(程式碼)給實際做出來。 為什麼要這麼麻煩?舉這個入境表格的例子,大家應該就立刻明白了。由於每個旅客的個人資料都是不同的,入境表格只能預先做好格式,但沒辦法幫大家填表,必須要透過每個物件自行以複寫來實作,這張表格才能發揮效用。 抽象類別或抽象方法在大型、多人協作的工作專案中比較有用,小型程式直接實作就好了。抽象類別或抽象方法可以幫助我們規劃程式的架構,讓多人合作的大型軟體專案或軟體工程變得更有架構和規劃,更有系統性,是屬於「架構設計」的功能。抽象類別或抽象方法可以方便上層的領導人員規劃程式架構,指派工作給各個軟體工程師去實作出來,並且可以更明確的劃分責任範圍,讓除錯與維護更有效率(例如主管罵說:這個方法(method)的實作亂七八糟,是誰寫的code?給我重寫)。 就好像公司創立初期業務量少,老闆一人就可以實作公司所有的大小事務。不過當公司發展到一定的規模時,老闆就必須制定良好的公司組織結構和章程規範、制定工作目標(抽象),並雇用員工來實踐所下達的任務(實作),而每個員工各司其職,做不一樣的事情,最後整合起來完成目標。 由於抽象類別包含了至少一個抽象方法,抽象方法沒有實作的程式碼,因此無法直接被實體化,所以抽象類別(abstract class)必須先以「繼承」的方式轉為一般的類別(class),然後在(一般的)類別中把抽象方法給實作出來,之後再實體化產生物件,讓物件使用(具體的)方法。 宣告抽象類別,實際的程式碼如下: abstract class Animal { // 以「abstract」關鍵字,定義一個抽象類別Animal String name ; // 定義字串變數name abstract void cry(); // 以「abstract」修飾子,定義抽象方法cry() } class Cat extends Animal { //(一般)類別Cat繼承自抽象類別Animal void cry(){ // 在一般類別中以複寫實作抽象方法(此時不用加 @override) System.out.println(”喵喵叫”); // 方法的實作程式碼 } } class Dog extends Animal { void cry(){ System.out.println(”汪汪叫”); // 每個類別都能把抽象方法複寫、實作成不同的功能 } } class Chicken extends Animal { void cry(){ System.out.println(”咕咕咕 ”); // 每個類別都能把抽象方法複寫、實作成不同的功能 } } 其餘依此類推。 ----------------------------------------------------------------------------------------- 介面(interface): 「介面」(interface)是JAVA很有名的功能,其實它和抽象類別(abstract class)很像。我們知道類別(class)是張設計圖,經過new運算子和建構子(constructor)做了實體化之後可以產生具體的物件,物件便可執行所擁有的方法(method)和其他物件進行互動。 介面是定義不同類別之間的一致行為,也就是一些共同的屬性(常數)或抽象方法。 就像不同作業系統的電腦,經由TCP / IP介面,一樣可以建立連線。 我們在類別(class)裡面會清楚的把所有屬性(成員變數)、以及方法(成員函數)的定義明確寫出來,所以一旦實體化之後,物件就能直接使用這個方法。 而介面(interface)的意思就是,我們在類別中的方法(成員函數)「全部」都寫成「抽象方法」,以及其屬性(成員變數)「全部」都設為「常數」。 剛才入境表格的例子就是一個介面(interface)的典型範例,由於每個旅客的「姓名」、「護照號碼」、「出生年月日」…等基本資料都不同,我們無法在定義類別(class)的時候就把它們填上,於是就只能定義一個「空表格介面」,並且在之後讓每個旅客自己去實作它們各自的方法。或者換個角度來說同一件事:我們把所有旅客所共有的抽象方法(例如姓名、護照號碼、出生年月日…)給「抽取」出來做成介面,讓每個旅客自己去實作各自的抽象方法。 實際撰寫介面(interface)的程式碼,和撰寫類別差不多,如下: interface EmptyForm { // 定義一個名為空表格(Empty form)的介面 public static final int a = 5 ; // 介面中只能定義常數(此處用final修飾子) public abstract void write(); // 介面中的抽象方法,前面要加上「abstract」修飾子 } 不過這邊有個小技巧,由於介面裡面的屬性和方法一定「全部」都是「常數」和「抽象方法」,也一定要是public的(不然怎麼讓外界實作?),因此所有的修飾子都可以省略不寫(寫了也無妨),如下: interface EmptyForm { int a = 5 ; // 所有的修飾子都可以省略不寫 void write(); // 所有的修飾子都可以省略不寫 } 以上注意,介面名稱的首個英文字母,習慣上像類別名稱一樣會使用大寫,便於區別。 ----------------------------------------------------------------------------------------- 介面的實作: 那,要怎麼實作介面呢?也就是如何來「填表」呢?由於介面全部都是抽象方法,因此無法直接實體化產生物件。所以,要先把介面轉換為一般類別(class),再透過(一般)類別複寫掉介面的抽象方法,此時會使用到implements(實作)關鍵字: class form implements EmptyForm { // 定義一個名為form的類別,來實作EmptyForm介面 public void write(){ // 在form類別中實作介面EmptyForm的抽象方法write() System.out.println(”護照號碼123456789”); // write()的實作程式碼 } } 介面經過類別的implements之後,我們就可以像平常一樣,從類別(class)去實體化產生物件。 JAVA還提供了一個很方便的功能,就是一個(一般的)類別可以同時實作多個介面,也就是一個類別可以同時把多個不同介面中的抽象方法彙整起來,並一次實作出來,再實體化產生物件。  註:這和多重繼承是不一樣的概念喔。 實際的程式碼如下: class form implements EmptyFormA , EmptyFormB , EmptyFormC { // 介面的名稱用逗號做區隔 … … … … … … … … … // 在form類別中實作三個介面的抽象方法 } 嗯,你說已經忘記之前寫過的介面,還要回頭查看介面中有那些抽象方法還沒有被實作?沒問題,這些IDE(整合開發環境Integrated Development Environment,例如Eclipse、IntelliJ IDEA、NetBeans …)都已經幫你想到了,IDE會幫你記住介面中還有哪些抽象方法還沒有被實作,然後適時提醒你,並且可以開啟子視窗查看目前有哪些抽象方法已經有實作、而哪些還沒被實作,甚至還可以讓IDE幫你把尚未被實作的抽象方法給一次全部列出來讓你「填空」,真是非常方便。 ----------------------------------------------------------------------------------------- 介面的繼承: 介面就和類別一樣,也可以使用繼承功能,產生父介面與子介面。介面繼承的語法和類別一樣,也是用extends關鍵字: interface A { // 定義一個(父)介面A int e ; void f(); } interface B extends A { // 定義一個子介面B int g; void h(); } 不過,由於介面裡面的內容比較單純,只是一個空殼,所以做「介面的多重繼承」比較不會像類別的多重繼承那樣複雜、混亂,所以可以放心使用。介面的多重繼承的程式碼為: interface X extends A , B , C { // 介面的名稱用逗號做區隔 … … … } 這樣一來,介面X就會同時擁有介面A、B、C三者的常數屬性和抽象方法了。 還有一種比較進階的複合語法,就是當一個(一般)類別需要同時繼承自某個類別,並且同時實作介面,語法寫作: class C extends A implements B { // 必須把extends寫在implements前面 … … … } 其中C是子類別,A是父類別,而B是介面名稱。不過,建議還不太熟練的讀者不一定要這麼寫,老實的分開寫也沒關係。 JAVA如何用介面(間接的)實現多重繼承? 一個類別可以同時實作多個介面,代表可以用介面的方式來間接的實現多重繼承。 public class Circle extends Shape implements ShapeInterface , ShapeInfo { … … … public void area(){ // area(){ 方法的實作程式碼 }; public void perimeter(){ // perimeter(){ 方法的實作程式碼 }; public void show(){ // show(){ 方法的實作程式碼 }; } 上述Circle類別繼承Shape抽象類別,並且同時實作兩個介面(ShapeInterface、ShapeInfo)的三個抽象方法,就是間接的透過介面,做到多重繼承。 ----------------------------------------------------------------------------------------- 小整理: (一般)類別(class): 包含成員變數和一般的(具體)方法。 抽象類別(abstract class): 包含至少一個抽象方法,不過也可以包含一般的(具體)方法。 抽象類別的實作: 靠繼承到一般類別(關鍵字extends)。 介面(interface): 介面的「全部」成員都是「常數」和「抽象方法」。 介面的實作: 靠一般類別的實作(關鍵字implements)。