# OOP Note ###### tags: `Design Pattern` [TOC] ## 基本概念 ### 類別(Class) 在程式語言中,類別定義一件事物的抽象特點。類別的定義包含了資料的形式(屬性, Field)以及對資料的操作(方法, Method)。我們也可以想像成類別是汽車的設計藍圖(blueprint),其中我們可以在這張藍圖定義抽象的內容(也就是屬性、方法),例如汽車的廠牌、汽車的車名以及馬力和取得汽車資訊等。 ```= class Car{ public String brand; public String type; public int hp; public String getInformation(){ return "廠牌"+brand+"型號"+type+"馬力"+hp; } } ``` ### 物件(Object) 物件也就是類別的實例,也就是說有了類別這張藍圖我們可以在程式中產生許多汽車類別的資料,而這些資料彼此之間不互相影響,每一個皆是獨立的。 ```= //宣告類別為Car的資料,並給定BMW變數 Car BMW = new Car("BMW","X5",340); //宣告類別為Car的資料,並給定Porsche變數 Car Porsche = new Car("Porsche","Cayenne",300); BMW.getInformation();//印出資訊 Porsche.getInformation();//印出資訊 ``` ## 四大特性 ### 1. 封裝 (Encapsulation) 即是將物件內部的資料隱藏起來,只能透過物件本身所提供的介面(interface)取得物件內部屬性或者方法,物件內部的細節資料或者邏輯則隱藏起來,其他物件即無法瞭解此物件的內部細節,若不經過允許之窗口(即此物件提供之方法)便無從更動此物件內之資料。簡白的說,==對一件事情只需要理解他的外在就好,不需要了解裡面內部的構造。== 例如:由先前的例子,我們可以透過getInformation()取得車子資訊,然而我們並不需要知道他是如何取得資訊的。 ```= BMW.getInformation();//印出資訊 Porsche.getInformation();//印出資訊 ``` ### 2. 繼承 (Inheritance) 在某種情況下,一個類別會有「子類別」。子類別比原本的類別(稱為父類別)要更加具體化,也就是說子類別繼承了父類別。例如:計程車(子類別)繼承了汽車(父類別)原有的屬性以及方法,也新增了自己特有的屬性(driverName)。 ```= class Car{ public String brand; public String type; public int hp; public String getInformation(){ return "廠牌"+brand+"型號"+type+"馬力"+hp; } } class Taxi extends Car{ public String driverName; } ``` ### 3. 多型 (Polymorphism) 簡單來說就是相同名稱的方法(Method),==**多個相同名稱的方法,傳入不同的參數,會執行不同的敘述。**== 多型(Polymorphism)則包含多載(Overloading)和複寫(Overriding)。 #### **多載(Overloading)** — 是指說在**相同類別**中,定義**名稱相同**,但是**參數個數不同**,或是**參數型態不同**的函式,這樣就可以利用參數個數或者參數型態,呼叫到對應的方法。例如:一個計算面積的方法,如果傳入一個參數,就當正方形來算面積;傳入兩個參數,就當成長方形來算面積。 ```= public int computeArea(int length){ return length * length; } public int computeArea(int length, int width){ return length * width; } ``` #### **複寫(Overriding)** — 是指覆寫掉父類別中的函式。例如:動物類別(父類別) getLegs()方法被鳥類別(子類別)覆蓋。 ```= class Animal{ protected int legs = 1; protected virtual int getLegs(){ return legs*4; } } class Bird extends Animal{ protected override int getLegs(){ return legs*2; } } ``` ### 4. 資料抽像化 (Data Abstraction) 抽象(Abstraction)在物件導向程式設計中是用來將成員屬性(Attributes)與成員方法(Methods)的實作細節隱藏起來的機制,抽象的優點與思維有一部分已經在封裝中被我們體現出來:對使用者而言,可以在沒有檢視過類別程式碼的情況下,就能透過屬性檢視、方法呼叫來運用類別所包含的功能。 另一部分抽象所蘊涵的特性,是提供類別開發者一種可以宣告成員方法而不實作細節的機制,稱為抽象方法(Abstract method),假如一個類別的宣告中包含有抽象方法,則該類別就被稱為抽象類別(Abstract class)。而由於其中含有未完成的抽象方法,所以抽象類別只能拿來繼承,等待新類別能夠在繼承後完成該抽象方法,而不能夠初始化。 簡述 >一個Animal的類別,但在現實生活中動物只是一個概念,並沒有一種生物叫作動物。 狗、鳥、魚都是實際存在於世界上的實體,他們都是動物,現實中卻沒有一個實體叫作動物。 把動物視為一種『概念』,他是抽象的,不存在於世界上的。 而Dog、Bird、Fish都具備這個概念的特質,所以繼承Animal。 撰寫抽象類別 ```= abstract class Animal{ int height,weight; void move(){ System.out.println("move...move..."); } } ``` 實體化抽象類別 ```= class Test { public static void main(String[] args) { Animal ani = new Animal(); }// end of main(String[]) }// end of class Test ``` 執行結果 ```= Exception in thread "main" java.lang.Error: Unresolved compilation problem: Cannot instantiate the type Animal ``` 發生編譯錯誤,因為 **==抽象類別無法被實體化==** 。 錯誤原因 : **==抽象類別是用來被繼承的==** ```= abstract class Animal { // 成員定義 } class Dog extends Animal { // 成員定義 } class Bird extends Animal { // 成員定義 } class Fish extends Animal { // 成員定義 } ``` #### 抽象方法 abstract method 利用修飾子abstract,可以使方法變成抽象方法, **==抽象方法只能寫方法的原型,無法定義方法的本體==**。 >方法的原型? 只定義修飾子、回傳值型態、方法名稱、參數型態,而沒有『大括號{}』的部分就是方法原型。 方法的本體就是用『大括號{}』定義的東西,有寫{}就算有定義,不管裡面有沒有程式敘述。 使用格式 : ```= abstract <修飾子> 回傳型態 方法名稱(<參數...>); ``` 使用範例 : ```= abstract void eat(); ``` 錯誤範例 : ```= abstract void eat(){ // 編譯錯誤:Abstract methods do not specify a body // 不管有沒有寫東西,都編譯錯誤,抽象方法不能定義方法本體 } ``` 衍生問題與解法 : 抽象方法本身沒有本體,只有原型的方法無法實做任何事,故需要有類別來繼承抽象類別並覆寫抽象方法。 範例 : ```= abstract class Animal { abstract void eat(); } class Dog extends Animal{ void eat(){ System.out.println("啃骨頭..."); } } ``` 母類別Animal定義了一個抽象方法eat(),宣告這個eat()方法回傳值是void且不帶任何參數。 子類別必須覆寫這個方法,為這個方法定義本體,也就是『實作implement』這個方法。 :::warning **注意** : 抽象方法只能定義在抽象類別中。 ::: 有了抽象方法,那我們的抽象類別就可以定義的更簡潔了。 範例程式: ```= abstract class Animal{ int weight,heigh; void setValue(int w,int h){ this.weight = w; this.height = h; } abstract void eat(); } class Dog extends Animal{ void eat(){ System.out.println("啃骨頭..."); } } class Bird extends Animal{ void eat(){ System.out.println("早起吃蟲..."); } } ``` :::success 關鍵字abstract,抽象,可以修飾: 1. 類別 讓類別變成抽象類別,不能建立實體,專門用來『被繼承』。 1. 方法 讓方法變成抽象方法,只能定義原型,專門用來『被覆寫』。 1. 其中抽象方法只能被定義在抽象類別中。 ::: #### interface 現實生活中,介面指的是的一個「統一的」、「標準的」規格,對於產品,或是物品進行使用上的規範等。 **舉例說明 :** 冰箱跟烤箱都是常見的電器設備,然而台灣電力公司要如何供給一般家庭所需的電力呢? 沒錯,就是110/220伏特的交流電,而且有一個標準的電力接頭,冰箱跟烤箱只需要接受這個電力規格,就可以使用,不管內部作了什麼處理,使用者只需要為他們接上制訂好的電源。 所以我們可以說,冰箱跟烤箱擁有相同的『行為』,在這裡指的就是接受並運用這個110/220伏特的交流電。 **為什麼需要介面?** 假設電器設備指的是可以從台灣電力公司取得需要的電源。 冰箱跟烤箱都可以接受台灣電力公司的電源,這兩個東西都可以視為電器設備,但這兩個東西在繼承關係上可說是相差很遠的。 例如: 物件 -> 冷藏設備 -> 冰箱。 物件 -> 加熱設備 -> 烤箱。 由於Java不允許多重繼承(任何類別只能有一個父類別),所以沒辦法讓冰箱既是冷藏設備又是電器設備。 :::danger ```= class 冰箱 extends 冷藏設備,電器設備{ // 編譯錯誤,不允許多重繼承 } ``` ::: 冰箱跟烤箱只有『使用台電提供的電』這個共通的行為,因此我們把電器設備定義為介面,如此一來冰箱跟烤箱都可以『實作』這個介面,從而具有這個介面的特性,台電也很清楚的知道只要送出的電可以讓『電器設備』這個介面使用就好,任何需要電的設備只要符合這個介面制訂的規則,就可以獲得電力。 :::success ```= / 繼承冷藏設備,實作電器設備這個介面 class 冰箱 extends 冷藏設備 implements 電氣設備{ / // 成員宣告 } ``` ::: **宣告介面 Declare interface** 宣告方式: ```= [修飾子] interface 介面名稱 { // 成員定義 } ``` 範例程式: ```= public interface Power{ void getPowerFromTP(); } ``` :::warning 介面的宣告就是使用關鍵字interface,跟類別很像都是走差不多的格式,其中方法的定義只能宣告『方法的原型』,就像在『抽象』的章節中提到的抽象方法一樣。 ::: **實作介面 implements interface** 利用關鍵字implements,來讓類別實作某個介面,類別需要定義好該介面所制定的方法。 範例程式: ```= class Refrigerator implements Power{ public void getPowerFromTP(){ System.out.println("轉換..轉換....") } } ``` :::warning 必須實作『所有』該介面宣告的方法,否則會編譯錯誤。 ::: 此時這個類別就可以被視為該介面來使用。 ```= Power obj = new Refrigerator(); obj.getPowerFromTP(); ``` 執行結果: ```= 轉換..轉換.... ``` **實作多個介面** 在Java不允許多重繼承,但同一個類別可以實作多個介面,算是彌補了不能多重繼承所帶來的不方便。 實作多個介面用『,』隔開,寫上介面名稱,並且該類別必須實作每個介面所制定的方法。 使用格式: ```= class 類別名稱 implements 介面1,介面2,...介面n{ // 成員定義 } ``` 範例程式: ```= interface A{ void Amethod(); } interface B{ void Bmethod(); } class MyClass implements A,B { public void Amethod(){ } public void Bmethod(){ } } ``` **內訂的修飾子** 在interface中定義的欄位、方法都有規定好的修飾子,可以寫也可以不寫,但不能衝突。 介面方法的修飾: ```= public abstract 回傳型態 方法名稱(); ``` ```= interface MyInterface{ public abstract void A(); // ok public void B(); // ok,編譯完會自行在執行檔加上 public abstract void C(); // ok,編譯完會自行在執行檔加上 public abstract private void D(); // no ok, 編譯錯誤,修飾子不正確 void E(){}; // no ok, 編譯錯誤,只能定義原型,不行定義方法本體 } ``` 介面欄位的修飾: ```= public static final 資料型態 變數名稱 = 值; ``` 跟方法一樣,可以省略修飾子,但不能與之衝突。 ```= interface Qoo{ int value = 10; public int value2 = 20; public static int value3 = 30; public static final int value4 = 40; // 以上四行敘述,編譯完成後在執行檔中修飾子均為 public static final int value5; // 編譯錯誤,必須給訂初始值 private int value6 = 60; // 編譯錯誤,private與public衝突 } ``` **同名欄位的存取** 一個類別可以實作多個介面,最多繼承一個類別,所以加上自己定義的欄位,可能會有許多相同名字的變數,該如何存取? ```= interface Inter1 { int value = 10; } interface Inter2 { int value = 20; } class Father{ int value = 30; } class A extends Father implements Inter1,Inter2{ int value = 40; } ``` 存取每個同名變數的範例程式: ```= class Test { public static void main(String[] args) { A a = new A(); System.out.println( Inter1.value ); // 以介面名稱存取,因為是static修飾 System.out.println( Inter2.value ); // 同上 System.out.println( ((Father)a).value ); // 先把物件a轉型成該父類別,再存取 System.out.println( a.value ); // 直接以物件存取 }// end of main(String[]) }// end of class Test ``` 執行結果: ```= 10 20 30 40 ``` **同名方法的實作** 一個類別可以實作多個介面,但介面中如果有相同的方法呢? ```= interface A{ void method(); } interface B{ void method(); int method(int a); } class MyClass implements A,B{ public int method(int a) { //... } public void method() { //... } } ``` :::warning 根據方法的多載,相同名稱、不同參數即視為不同的方法,一模一樣的方法實作一個即可,關鍵點是每個不同的方法都必須實作。 ::: **介面的繼承** 沒錯,介面也可以繼承,而且還允許多重繼承! ```= interface 介面名稱 extends 介面1,介面2,...,介面n{ } ``` 彼此繼承的結果就是,實作該介面的類別必須實作每個定義的方法。 ```= interface A{ void a(); } interface B{ void b(); } interface C extends A,B{ // 介面的繼承,繼承多個介面以逗號『,』隔開 void c(); } class MyClass implements C{ // 必須實作介面C及其所有父類別定義的方法 public void a() { } public void b() { } public void c() { } } ``` **抽象類別實作介面** 抽象類別也是個類別,當然也可以實作介面。 然而抽象類別中可以定義該介面宣告的方法的本體,也可以不定義。沒定義的話就是由繼承這個抽象類別的子類別要實作所有抽象方法。 ```= interface A{ void a(); void b(); } abstract class AbsClass implements A{ public void b(){ System.out.println("hello b~"); } abstract void c(); } class MyClass extends AbsClass{ public void a() { } // 方法b()已經在AbsClass實作,MyClass不需要再實作,當然也可以再覆寫b() public void c() { } } ``` :::warning 原則就是,非抽象類別要實作所有未定義的方法。 ::: #### 抽象類別與介面的比較 Abstract Classes Compared to Interfaces 抽象類別與介面有點像,兩個都不行被實體化成物件。 | 抽象類別 | | -------- | | 用於被繼承,子類別要實作定義的抽象方法。 | | 抽象類別中可以定義完整的方法(方法本體),也可以只定義方法原型。 | | 可以定義完整的資料欄位供繼承類別使用。 | | 設計中心以資料為主體。| | 介面 | | -------- | | 用於被實作,子類別要實作定義的方法。 | | 介面中只能定義方法原型,不能有方法本體。 | | 方法的修飾子必為public abstract,欄位的修飾子必為public static final,可省略不可衝突。| | 定義的資料欄位用於作為常數使用。(因為修飾子為public static final)| | 設計中心以方法(行為)為主體。| | 一般來說有共同的概念可以繼承相同的抽象父類別,| | 若只是行為相同以介面來設計會比較恰當。| :::info 實務上先考慮介面的劃分會比較方便,畢竟類別可以繼承多個介面,需要用到層層的欄位概念再使用類別去繼承。 :::