什麼是介面
現實生活中,介面
指的是的一個「統一的」、「標準的」規格,對於產品,或是物品進行使用上的規範等。
例如 USB
就是一個介面,無論這項電子產品(假設是隨身碟)內部怎麼設計、電路怎麼接、晶片中的資料怎麼存取最後只要把接頭做成 USB
的長方型樣子,遵照它的接腳標準,就可以插進電腦中進行使用。
對於電腦使用者來說,這個隨身碟怎麼設計的不用管,未來我想要換其他的產品使用,例如把 隨身碟 換成 光碟機 來使用那麼我只要把隨身碟從 USB 洞口裡面拔出來,插上光碟機的 USB 線就可以使用這台光碟機。
換個例子,以生活中的電源插座為例,今天我想要使用吹風機時,只要把電線插進牆上的兩孔插座,就可以使用換成吸塵器?沒問題!只要插座是兩個直線的形狀(台灣規格),就可以插進牆上使用。
你發現了嗎?不論我們用什麼電子產品,只要符合插頭形狀的,就可以互相替換使用。
這就是定義 介面
帶給我們的好處。
如下表所示
未來我們開發了新的產品,只要符合 USB 的標準,電腦就可以使用這項產品。
如果沒有介面
假設電腦中不存在 USB
的這個介面,那麼會有什麼問題呢?
電腦主機上可能會有很多的插孔,像是滑鼠插孔、光碟機插孔、鍵盤插孔、隨身碟插孔... 等等。而如果今天出現了一個新產品,但是電腦上沒有對應到他的插孔,想要使用這個新產品,你有兩個選擇:
- 硬生生的把電腦拆開,在已經設計好的主機板想辦法找兩個接點出來,然後在機殼上鑿個洞,把新的插孔裝上去。但是下次又有新產品出現的時候,你就要再鑿一次洞。
- 買一個新的,有此插孔的電腦,但是下次要用新產品的時候,你就得再買一次新電腦。
上面兩個方法,不管哪一個都是天馬行空,就算真的選了一個方法,又能重複幾次呢?(機殼又能禁得起再挖幾次洞呢?)
這時候,我們就需要一個統一的標準、統一的產品銜接方法,
這個就叫做「介面」(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()
這個方法,但是 Mouse
與 Keyboard
的實作方法不同,這個就是定義介面帶給我們的好處,如同我們在 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
可以讓我們把程式邏輯跟實作分離(抽象化),主程式不用管類別怎麼實作,只要顧好自己的流程,呼叫他們定義在介面中的方法就行。 - 未來在修改程式方面,也只需要實作該介面,變成一個新的類別,就可以無痛擴充現有的程式,而不用回去改到主程式流程。
- 主程式應該要依賴
介面
,而不是依賴某一個類別。如果依賴某一個類別(例如電腦只依賴滑鼠),會造成耦合度太高,抽換不易,擴充困難。反而是依賴在一個抽象的介面上,只需要實作它的方法,就可以拿進來程式使用。