Benyi Hsia

我是Benyi,這裡存放關於資訊科技的文章

[物件導向] 何謂介面(interface)?

| Comments

什麼是介面

現實生活中,介面 指的是的一個「統一的」、「標準的」規格,對於產品,或是物品進行使用上的規範等。

例如 USB 就是一個介面,無論這項電子產品(假設是隨身碟)內部怎麼設計、電路怎麼接、晶片中的資料怎麼存取最後只要把接頭做成 USB 的長方型樣子,遵照它的接腳標準,就可以插進電腦中進行使用。

對於電腦使用者來說,這個隨身碟怎麼設計的不用管,未來我想要換其他的產品使用,例如把 隨身碟 換成 光碟機 來使用那麼我只要把隨身碟從 USB 洞口裡面拔出來,插上光碟機的 USB 線就可以使用這台光碟機。

換個例子,以生活中的電源插座為例,今天我想要使用吹風機時,只要把電線插進牆上的兩孔插座,就可以使用換成吸塵器?沒問題!只要插座是兩個直線的形狀(台灣規格),就可以插進牆上使用。

你發現了嗎?不論我們用什麼電子產品,只要符合插頭形狀的,就可以互相替換使用。

這就是定義 介面 帶給我們的好處。

如下表所示

未來我們開發了新的產品,只要符合 USB 的標準,電腦就可以使用這項產品。

如果沒有介面

假設電腦中不存在 USB 的這個介面,那麼會有什麼問題呢?

電腦主機上可能會有很多的插孔,像是滑鼠插孔、光碟機插孔、鍵盤插孔、隨身碟插孔... 等等。而如果今天出現了一個新產品,但是電腦上沒有對應到他的插孔,想要使用這個新產品,你有兩個選擇:

  1. 硬生生的把電腦拆開,在已經設計好的主機板想辦法找兩個接點出來,然後在機殼上鑿個洞,把新的插孔裝上去。但是下次又有新產品出現的時候,你就要再鑿一次洞。
  2. 買一個新的,有此插孔的電腦,但是下次要用新產品的時候,你就得再買一次新電腦。

上面兩個方法,不管哪一個都是天馬行空,就算真的選了一個方法,又能重複幾次呢?(機殼又能禁得起再挖幾次洞呢?)

這時候,我們就需要一個統一的標準、統一的產品銜接方法
這個就叫做「介面」(interface)。

介面與實作(implementation)

介面可以讓我們在設計程式的時候,將該有的實作方法定義好,也就是只描述一個抽象的方法名稱。換句話說,每個產品都有每個產品各自的特性和他們的特色,但是只要實作這些方法的裝置,都可以叫作 USB 裝置。所以,支援 USB 的電腦,無論換了什麼東西,只要插得進 USB 插座的,都可以使用。(電腦依賴在 USB 的介面(interface)上,而不是依賴在 USB 滑鼠、USB 鍵盤… 等裝置上。)

USB 這個介面中,可能會有這些方法

  • boot() 裝置開機
  • isConnected() 裝置是否有連線
  • getErrorMessage() 裝置連線不成功的錯誤訊息
  • getPower() 裝置取得電源
  • getData() 裝置取得需要使用到的資料
  • saveData() 裝置把資料儲存起來
  • shutdown() 裝置關機

當定義好 介面(interface)之後,接下來我們要做的,就是在每個我們生產的裝置上實作這些方法。反過來說,只要實作這些方法的裝置,都可以稱作 USB 介面

抽象化(abstraction)與物件導向

由於電腦認得 USB 介面,未來只要有新的裝置插上電腦的時候,可能會先呼叫isConnected() 方法檢查是否有接上成功,直接呼叫每個裝置的 getPower() 方法取得電源,接著呼叫 boot() 方法讓它們開機等等,等到使用完畢之後,最後呼叫 shutdown() 方法讓這個裝置關機。

發現了嗎?在我所描述的上面這個過程中,有沒有提到「是什麼裝置」?有沒有提到這個裝置的 getPower()(取得電源)方法應該怎麼做? 沒有。這個就是我們利用介面把程式進行抽象化(abstraction)

抽象化指的是「只描述一個大概的流程跟邏輯,真正實現的方法交由底下各個裝置去做」

設計程式的時候,如果這個世界上只有滑鼠會接上電腦使用,那我們只要在電腦的邏輯中寫下處理「滑鼠」的程式就好了。

但事實不是,且未來也還會有更多的裝置被創造、發明出來。如果一開始就將處理「滑鼠」的邏輯寫在電腦的類別裡,會發生什麼事呢? 未來有新的裝置加入,我們就必須回去改這個程式,而且加的愈多,改的愈多。

正確的作法,應該是讓電腦依賴在 USB 這個 介面(interface)上,而不是單一的裝置,接著,在控制邏輯中呼叫他們各別的方法即可。

步驟1:先定義一個 USB 的介面 USBInterface

interface USBInterface
{

    /** 裝置開機 */
    public function boot();
    
    /** 裝置是否有連線 */
    public function isConnected();
    
    /** 裝置連線不成功的錯誤訊息 */
    public function getErrorMessage();
    
    /** 裝置取得電源 */
    public function getPower();

    /** 裝置取得需要使用到的資料 */
    public function getData();

    /** 裝置把資料儲存起來 */
    public function saveData();
    
    /** 裝置關機 */
    public function shutdown();
}

注意到了嗎? 雖然定義了一個 USB 裝置該有哪些方法,但並沒有寫任何的程式碼,也就是不包含實作(這樣才可以分離抽象的邏輯,而不是綁死在某一個裝置上)

步驟2:實作各個裝置來自介面的方法

class Mouse implements USBInterface
{
    /** 實作 USB 滑鼠的開啟方法 */
    public function boot()
    {
        if ( $this->isBoot() ) {
            return "滑鼠開啟成功";
        } else {
            $this->bootRetry();
        }
    }
    
    /** 實作其他方法 (略) */
    ...
}

class Keyboard implements USBInterface
{
    /** 實作 USB 鍵盤的開機方法 */
    public function boot()
    {
        if ( $this->bluetoothConnect() ) 
        {
            return "已連線到藍芽鍵盤";
                    
        } else {
        
            if ( $this->bootKeyboard() ) 
            {
                return "鍵盤開啟成功"    
            }

        }
    }

    /** 實作其他方法 (略) */
    ...
}

在這個步驟,只要把每個我們想新增的裝置,都宣告實作一個 USBInterface 介面,然後開始寫我們每個裝置裡面該有的實現方法。

你可以看到,雖然都是 boot() 這個方法,但是 MouseKeyboard 的實作方法不同,這個就是定義介面帶給我們的好處,如同我們在 USBInterface 這個介面中描述「身為一個 USB 裝置應該要有什麼方法」,往後這兩個裝置想要成為 USB 裝置的時候,就必須實作這些方法。

步驟3:撰寫主程式邏輯

class Computer
{

    public function __construct(USBInterface $device)
    {
        $this->device = $device;
    }

    /** 
     * 連接到裝置 
     **/
    public function connectDevice()
    {
        /** 裝置開機 */
        $this->device->boot();

        /** 如果未連線成功的話,傳回各裝置的錯誤訊息 */
        if ( !$this->device->isConnected() ) {
            return $this->device->getErrorMessage();
        }

        /** 取得電力與資料 */
        $this->device->getPower();
        $this->device->getData();

        /** 儲存資料成功,關機 */
        $result = $this->device->saveData();
        if ( true === $result ) {
            $this->device->shutdown();
        }

    }
}

我們在 Computer 這個類別依賴了 USBInterface 這個介面。當未來我們要連接到新的裝置時,建立 Computer 實體所需要的 $device 參數,必須是實作 USBInterface 這個介面的類別。

例如,電腦今天要連接滑鼠上來,我們會這樣呼叫

/** 建立一個 USB 滑鼠的實體 */
$device = new Mouse;

/** 建立一個電腦的實體 */
$computer = new Computer($device);
/** 連接到裝置 */
$computer->connectDevice();

而我們如果要改成連接其他的裝置,也只要修改 $device 這個實體,無論是什麼,符合 USBInterface 介面的,都可以傳遞進來使用不用怕出錯,因為只要是實作 USBInterface 這個介面的類別,都一定會有 boot()getPower() … 等等這些方法。主程式的邏輯中,也只要負責呼叫,管好自己的流程就行,不需要管到哪個裝置、怎麼實作的。

結論

  • 定義 介面 interface 可以讓我們把程式邏輯跟實作分離(抽象化),主程式不用管類別怎麼實作,只要顧好自己的流程,呼叫他們定義在介面中的方法就行。
  • 未來在修改程式方面,也只需要實作該介面,變成一個新的類別,就可以無痛擴充現有的程式,而不用回去改到主程式流程。
  • 主程式應該要依賴 介面,而不是依賴某一個類別。如果依賴某一個類別(例如電腦只依賴滑鼠),會造成耦合度太高抽換不易,擴充困難。反而是依賴在一個抽象的介面上,只需要實作它的方法,就可以拿進來程式使用。

Comments

comments powered by Disqus