---
# System prepended metadata

title: 抽象 - 介面 vs 繼承
tags: [Java 基礎]

---

---
title: '抽象 - 介面 vs 繼承'
disqus: kyleAlien
---

抽象 - 介面 vs 繼承
===

## Overview of Content

如有引用參考請詳註出處，感謝 :smile_cat: 

這裡不說明 `interface`、`abstract class` 的基礎用法、功能，找要分析比較介面與抽象

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入比較介面與抽象類：從多個角度剖析**](https://devtechascendancy.com/comparing-interfaces-abstract-classes/)
:::


[TOC]

## 介面 vs 抽象：從不同程面比較

繼承類別（`abstract class`）、介面（`interface`）都位於物件的繼承樹上層，都無法被實體化，但兩者有哪裡不一樣嘛？這就是這個小節要探討的，我們以不同的角度來看看者兩種抽象化的差異點…

### 比較角度：隱定性、可變性
    
* **介面由如合約**：它對外公佈給使用方，這會導致它有一個特性，**==介面必須穩定==，不可任意更改（不管是增加、刪除）合約**，否則會影響到所有實做的子類

    > 這裡我們不考慮 interface 的 `default` 方法

    ```java=
    interface FileReader {

        // 新增方法
        boolean isEmpty();

        int read(byte[] buffer);

    }

    // 子類必須實做 isEmpty 否則會導致編譯不過
    class DefaultFileReader implements FileReader {

        @Override
        public int read(byte[] buffer) {
            // TODO:
        }

    }
    ```
    :::info
    * [**介面隔離原則**](https://devtechascendancy.com/object-oriented-design-principles_2/#%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87_Interface_Segregation)（想了解更多請點進連結）

        所以在設計介面時必須要嚴加小心思考，進而產生了另一種設計界面的概念，**介面隔離（介面最小化）** 來讓介面更有可控性，降低使用代價
    :::

* **抽象具有可調性**：而相對比起來，抽象在更改時不一定會影響到子類（新增共有方法，也就是預設實做），相較介面，它更具有可調整性

    ```java=
    // 子類必須實做 isEmpty 否則會導致編譯不過
    abstract class CommonFileReader {

        // 新增共有實做方法，子類不一定需要實現
        protect boolean isValid(byte[] buffer) {
            // TODO:
        }

    }


    class UTF8FileReader extends CommonFileReader {

    }

    class UnicodeFilreReader extends CommonFileReader {

    } 
    ```

### 比較角度：複雜度、多實現

* **介面可以多實現、降低複雜度**：
    
    Java 的特性之一就是類只能單一繼承，而 **介面（`interface`）的實做可以有多個**；
    
    而以 JVM 的角度來看介面的方法，**介面方法由有實做類來完成該方法，不會有複寫的狀況，降低了 JVM 動態連結的複雜度**（對於效能的提昇）

    ```java=
    interface IShowMessage {

        void showMessage(String str);

    }

    interface ISettingMessage {

        void setMessage(String str);

    }
     // 可實做多個界面        
    class DefaultShowMessage implements IShowMessage, ISettingMessage {

        @Override
        public void showMessage(String str) {
            // TODO
        }

        @Override
        public void setMessage(String str) {
            // TODO
        }
    }
    ```

    :::warning
    * **Java 為何不設計多繼承**

        像是 `C++`、`Dart`... 等等語言的多繼承，在處理類的聯繫規則時會變得複雜，而 **Java 語言的設計理念則是降低語言的複雜度**
        
        > 所以也有人說 `Java` 其實是 `C++--`，也就時降低 C++ 的複雜度，只提高它的普片通用性
    :::

### 比較角度：可維護性、重構性

* **可維護性、重構性**：從已經有的（已設計好的）類別中，要再進行抽象並不容易，因為你只要抽象化所有的方法，其繼承的子類都必須實做新的方法
    
    > 這裡並不考慮在 `abstract class` 中的實做方法，目前只考慮「純抽象」

    ```java=
    // 已經設計好的類
    abstract class Firmware {
    
        // 要拓展（重構）的新方法，子類沒有實做是不行的
        abstract boolean update();

        abstract String version();

    }

    class HMI extends Firmware {

        @Override
        String version() {
            return "1.1.1";
        }

    }

    class LED extends Firmware {

        @Override
        String version() {
            return "0.3.8";
        }
    }
    ```
    
* 那要換做界面（`interface`）會比較好嗎？
    
    這又會牽扯到前面說明的概念，基於 **類可實現多介面** 所以我們可以透過設計界面來達成重構時的新抽象，這可以達到兩個好處
    
    1. **不必對已有的抽象類新增抽象**

        ```java=
        // 新功能使用介面拓展
        interface IUpdate {

            boolean update();

        }

        // 原有的類保持
        abstract class Firmware {

            abstract String version();

        }
        ```

    2. **只需要對需要的類進行界面的實做（繼承無法達到，因為 Java 不許多繼承），不用依賴多餘界面**

        ```java=
        // 針對需要的類進行實做即可
        class HMI extends Firmware implements IUpdate {

            @Override
            String version() {
                return "1.1.1";
            }

            @Override
            public boolean update() {
                // TODO
                return false;
            }
        }
    
        // 不需要的類不實做介面
        class LED extends Firmware {

            @Override
            String version() {
                return "0.3.8";
            }
        }
        ```

        :::success
        * 要如何為 **介面取名**？
            
            1. 首先我們要知道「取名」的重要性，它有關到之後程式的可讀性、可維護性
            
            2. 在語法上，**介面不會強迫它的實做必須與類名的實做在語意上有強烈關係**，介面名只須跟須拓展的類有關連性即可
            
                也就是說我們在為界面取名時，要取與該界面最相關的名稱，而不是與類最相關的名稱！
        :::

### 比較角度：耦合性

* **如果要選擇一個作為 ++方法宣告，選擇介面更為合適++**，原因如下
    
    1. **介面（`interface`）更加單純，不會參雜多餘的邏輯實做**
        
        抽象如果要過展就必須不斷的繼承下去，有很多時候會「被迫」拓展出不須的功能；**相對起來抽象更屬於實做 & 介面之間的產物**
    
        我們看看以下範例：這個範例中闡述的是抽象類繼承的「**強迫性**」
        ```java=
        abstract class Fan {

            abstract void turnOnFan();

            abstract void freshAir();

        }

        class NormalFan extends Fan {

            @Override
            void turnOnFan() {
                System.out.println("Start Fan~");
            }

            // 強迫繼承了不需要的功能抽象，只能使用拋出
            @Override
            void freshAir() {
                throw new UnsupportedOperationException();
            }

        }

        class LGAirPurifier extends Fan {

            @Override
            void turnOnFan() {
                System.out.println("Start LG Fan~");
            }

            @Override
            void freshAir() {
                System.out.println("Start LG fresh air~");
            }
        }
        ```
    
    2. **界面語法上的支援限制更少**：
        
        **一個類可以實做多個介面，也就是說它可以完成多個功能，並不像抽象類會受到限制**

        ```java=
        interface IFan {

            void turnOnFan();

        }

        interface IFreshAir {

            void freshAir();

        }

        class NormalFan2 implements IFan {

            @Override
            public void turnOnFan() {
                System.out.println("Start Fan~");
            }

        }

        class LGAirPurifier2 implements IFan, IFreshAir {

            @Override
            public void turnOnFan() {
                System.out.println("Start LG Fan~");
            }

            @Override
            public void freshAir() {
                System.out.println("Start LG fresh air~");
            }
        }
        ```


## 介面、抽象：選擇考量

在上面我們用不同的角度來比較界面跟抽象，似乎界面看起來更好一點？

不！**它們各有各的特點，應該各司其職**，在正確的時機使用它們才是重點，那接著我們就來看看，要如何選擇考量…

### 介面：對外服務

* **界面（`interface`）對外提供服務**：
    
    **抽象有分「層」，最高層的抽象就是界面，++介面應該作為與外界溝通的視窗++**，並且它傳達出的是一種 **契約規範**（依照規則就可以達到像對應的處理）

    不論大小系統之間，都應該使用界面進行互動，**這樣可以有效的提高鬆耦合**
    
    :::warning
    注意：界面需要謹慎思考，因為 **開出的介面不可任意修改**，也請記得善用 [**介面隔離原則**](https://devtechascendancy.com/object-oriented-design-principles_2/#%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87_Interface_Segregation)
    :::

* 我們看看以下範例，該如何使用界面對外提供功能，**並降低類與類之間的耦合度**：

    1. **`PowerManager` 子系統**：
        
        對外提供 `IPowerManager` 介面讓其他子系統使用，也就是對外提供給使用者時，是對外提供 `IPowerManager` 介面，不讓使用者關注內部實做，進而達到 **鬆耦合** 的特色（讓使用者依賴在界面合約上，而不是實做之上！）

        ```java=
        /*
         * public 層級
         */ 
        public interface IPowerManager {

            void lowPower();

            void typicalPower();

        }

        /**
         * package 層級
         */ 
        class DefaultPowerManager implements IPowerManager {

            @Override
            public void lowPower() {
                System.out.println("Low power mode.");
            }

            @Override
            public void typicalPower() {
                System.out.println("Typical power mode.");
            }

        }
        ```

    2. **`WindowManager` 子系統**
        
        * `IWindow` 界面用來顯示、關閉視窗：對外部暴露界面（`IWindow`），而實做 `DefaultManager` 透過 package 層級來對外隱藏！

            ```java=
            // 對內部提供功能，也可以用契約，但是不會像外部這麼嚴厲
            /*
             * public 層級
             */ 
            public interface IWindow {

                void showWindow();

                void closeWindow();

            }

            /**
             * package 層級
             */ 
            class DefaultManager implements IWindow {

                @Override
                public void showWindow() {
                    System.out.println("Show window.");
                }

                @Override
                public void closeWindow() {
                    System.out.println("Close window.");
                }

            }
            ```
        
        * `IWindowManager` 界面內部依賴抽象 `IWindow` 界面來達到隱藏實做細節類的目的，同時對外也只提供 `IWindowManager` 界面，對實做類同樣使用 package 層級來隱藏
            
            > 並且我們可以看到，在這樣的設計之下也隱藏了 `DefaultWindowManager` 依賴 `IPowerManager` 界面的細節！
            
            ```java=
            // 對外的保證契約
            /*
             * public 層級
             */ 
            public interface IWindowManager {

                void addWindow(IWindow window);

                void removeWindow(IWindow window);

            }

            /**
             * package 層級
             */ 
            class DefaultWindowManager implements IWindowManager {

                private final List<IWindow> windowList = new ArrayList<>();

                private final IPowerManager powerManager;

                public DefaultWindowManager(IPowerManager powerManager) {
                    this.powerManager = powerManager;
                }

                @Override
                public void addWindow(IWindow window) {
                    if(!windowList.contains(window)) {
                        windowList.add(window);
                    }

                    if (windowList.size() > 100) {
                        powerManager.lowPower();
                    } else {
                        powerManager.typicalPower();
                    }
                    
                    window.showWindow();

                }

                @Override
                public void removeWindow(IWindow window) {
                    windowList.remove(window);
                    
                    window.closeWindow();
                }

            }
            ```

    * **對外提供給使用者**：可以看到我們可以對外提供給使用者純界面的類型，而不需要對外暴露實做！

        ```java=
        public SystemWindow {
            public static final SystemWindow instance = new SystemWindow();
            
            private SystemWindow() { }
            
            // 對外提供界面
            private final IPowerManager powerManager = new DefaultPowerManager();
            
            // 對外提供界面
            public final IWindowManager windowManager = new DefaultWindowManager(IPowerManager);
            
        }
        ```


### 抽象類：對內擴充

* **抽象類（`abstract class`）繼承對內擴充**：
    
    抽象屬於介面、實做之間的 **半成品**，抽象除去各個子類的特點，它會盡可能的完成共通邏輯的實做；盡量使用在系統內部使用
    
    **當一個方法是為了特定的擴充（該擴充是給內部系統呼叫），而不是為了對外服務（外部呼叫該擴充則無用），那該類就可以使用抽象！**
    
* **對內擴充的範例**：
    
    1. **主系統如下**：透過介面來說明對外的承諾，這次在其中我們添加了一個 `VerifyRule` 類，用來給使用者擴充驗證方案

        ```java=
        interface IVerifyManager {

            boolean verify(String str, VerifyRule rule);

        }

        class DefaultVerifyManager implements IVerifyManager {

            @Override
            public boolean verify(String str, VerifyRule rule) {
                if (str == null || str.isEmpty()) {
                    return false;
                }
                
                // Maybe do something...

                return rule.startCheck(str);
            }

        }

        // 待驗證項目
        abstract class VerifyRule {

            protected VerifyRule next;

            // 可拓展的方法
            protected abstract boolean check(String str);

            // 對內統一方法
            final boolean startCheck(String str) {
                boolean currentCheck = this.check(str);
                if (!currentCheck) {
                    return false;
                }

                if (this.next != null) {
                    return this.next.startCheck(str);
                }

                return true;
            }

        }
        ```
        
        :::danger
        * 這裡記得，非對外提供擴充的方法要用 `final` 描述，意在於不讓外部類覆寫，保持類設計的安全性！
        :::

    2. **使用者拓展、擴充**：這個擴充的要點是，**它會讓內部系統經過邏輯驗證後，再去呼叫**，使用者不會知道該擴充被呼叫的時機點

        ```java=
        class JWTRule extends VerifyRule {

            public JWTRule() {
                this.next = new CRCRule();
            }

            @Override
            protected boolean check(String str) {
                return str.contains("JWT");
            }
        }

        class CRCRule extends VerifyRule {

            @Override
            protected boolean check(String str) {
                return str.contains("CRC");
            }
        }
        ```
    
## 更多的 Java 語言相關文章


### Java 語言深入

* 在這個系列中，我們深入探討了 Java 語言的各個方面，從基礎類型到異常處理，從運算子到物件創建與引用細節。點擊連結了解更多！

    :::info

    * [**深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/)
    * [**深入了解 Java 應用與編譯：從原始檔到命令產出 JavaDoc 文件 | JDK 結構**](https://devtechascendancy.com/java-compilation_jdk_javadoc_jar_guide/)

    * [**深入理解 Java 異常處理：從基礎概念到最佳實踐指南**](https://devtechascendancy.com/java-jvm-exception-handling-guide/)

    * [**深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較**](https://devtechascendancy.com/java-operators-modifiers-key-concepts_equals/)

    * [**深入探索 Java 物件創建與引用細節：Clone 和finalize 特性，以及強、軟、弱、虛引用**](https://devtechascendancy.com/java-object-creation_jvm-reference-details/)

    :::

### Java IO 相關文章

* 探索 Java IO 的奧秘，了解檔案操作、流處理、NIO等精彩內容！

    :::warning
    * [**Java File 操作指南：基礎屬性判斷、資料夾和檔案的創建、以及簡單示範**](https://devtechascendancy.com/java-file-operations-guide/)

    * [**深入探索 Java 編碼知識**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/)

    * [**深入理解 Java IO 操作：徹底了解流、讀寫、序列化與技巧**](https://devtechascendancy.com/deep-in-java-io-operations_stream-io/)

    * [**深入理解 Java NIO：緩衝、通道與編碼 | Buffer、Channel、Charset**](https://devtechascendancy.com/deep-dive-into-java-nio_buf-channel-charset/)
    :::

### 深入 Java 物件導向
* 探索 Java 物件導向的奧妙，掌握介面、抽象類、繼承等重要概念！

    :::danger
    * [**深入比較介面與抽象類：從多個角度剖析**](https://devtechascendancy.com/comparing-interfaces-abstract-classes/)

    * [**深度探究物件導向：繼承的利與弊 | Java、Kotlin 為例 | 最佳實踐 | 內部類細節**](https://devtechascendancy.com/deep-dive-into-oop-inheritance/)

    * [**「類」的生命週期、ClassLoader 加載 | JVM 與 Class | Java 為例**](https://devtechascendancy.com/class-lifecycle_classloader-exploration_jvm/)
    :::


    
## Appendix & FAQ

:::info
:::

###### tags: `Java 基礎` `Java 設計模式`
