剛開始在學習物件導向的時候,學習了 class、object、繼承、封裝等等的使用方式,但是在這之中最無法參透的是:
為什麼需要 interface?
interface
翻譯成中文是『介面』,程式語言中的物件跟『介面』看字面上的意思感覺好像是八桿子打不著的關係,所以一直無法理解 interface
的用途,所以不得不說自已也遇過不知道什麼時候需要 interface
,或者是只是覺得用了很帥才用,所以你問那時候的我為什麼要用,我也只能回答你:「因爲看大家都這樣子用,感覺這樣用很帥阿。」
所以今天我們就來聊聊為什麼需要 interface?什麼時候要使用 interface?
先講結論:
interface 就像是證照或職位,誰持有這個證照或職位後誰就要有能力執行這張證照或職位要求的技能。
例如說:如果我們看到醫生,先還沒看到醫生的名稱之前我們就會大概知道他有幫人看診的能力,我們看到廚師,就會知道他有烹飪的能力,看到某人多益考了 900 多分,就可以知道他英文強的不要不要的 😎
至於為什麼有這個結論?我們一樣先問為什麼要有 interface?不過照慣例我們用一個設計公司老闆的創業故事來當起手式:
為什麼要使用 interface
Brooky 是一個創業家,他想要創立一家名爲《給你一點顏色瞧瞧》的設計公司,所以在千辛萬苦地經歷了設立公司的流程後,公司終於確定可以營業了。當公司營運第一天,他面臨到的一個問題:
《給你一點顏色瞧瞧》沒有員工。
「對,沒有員工。」他看著偌大的辦公室對自己說,依稀都可以聽到自己講話的迴音。所以他想了一下需要怎麼樣子的員工呢?
- 會使用 Photoshop
- 會使用 Illustrator
他突然想起他的好朋友 Annie 好像剛好會這兩個技能,所以就找她來擔任《給你一點顏色瞧瞧》首席設計師。
所以關係圖如下:
// 《給你一點顏色瞧瞧》有限公司
class GiveYouALittleColorLimitedCompany
{
private $employees = []; public function onboard(Person $person)
{
array_push($this->employees, $person);
}
}// 創辦人 Brooky
class Brooky
{
private $company; public function __construct()
{
// 成立《給你一點顏色瞧瞧》有限公司
$this->company = new GiveYouALittleColorLimitedCompany();
} public function hire(Person $person)
{
// 僱用設計師進入《給你一點顏色瞧瞧》有限公司
$this->company->onboard($person);
}
}// 首席設計師 Annie
class Annie
{
// 使用 Photoshop 處理點陣圖檔的技能
public function photoshop($image)
{
return "用 PS 處理點陣圖檔 ..." . $image;
} // 使用 Illustrator 處理向量圖檔的技能
public function illustrator($image)
{
return "用 AI 處理向量圖檔 ..." . $image;
}
}
於是他們開門過了 5 天後,終於來了第一個客戶,是一家名叫《埃及少女》的旅行社公司,他們的業務都是帶客人到埃及參觀古文物古蹟,他們有想要重新設計 Logo 商標的需求,可能是因爲他們已經看膩了永遠都是法老的頭像當 Logo,所以想委託《給你一點顏色瞧瞧》協助設計 Logo,創辦人安珈娜十分的有想法,所以希望《給你一點顏色瞧瞧》照著她的想法提出兩個版本的設計,一個版本是向量圖形的 Logo,另外一個則是點陣圖形的 Logo,在經過了一分討論有定案後,他們變簽訂了契約,契約上大概是這樣子寫的:
甲方:給你一點顏色瞧瞧乙方:埃及少女乙方公司使用的的商標設計全權由甲方的 Annie 來協助規劃與設計。。。。
。。。
(契約是我亂掰的,別太認真正確與否 😅)
所以關係圖如下:
// 埃及少女有限公司
class EgyptGirl{
}// 創辦人 安珈娜
class Anjana extends Person
{
private $company; public function __construct()
{
// 成立《埃及少女》有限公司
$this->company = new EgyptGirl();
} public function request()
{
return '請求協助設計 Logo 商標';
}
public function receiveLogos(array $logos)
{
return '接收完成的 Logo 圖檔';
} public function confirm()
{
return '確認 Logo 完成';
}
}$brooky = new Brooky();$annie = new Annie();// Brooky 僱用 Annie
$brooky->hire($annie);/* 客戶安珈娜上門囉~ */
$anjana = new Anjana();// 安珈娜請求協助設計 Logo
$anjana->request();// Annie 開始製作向量版本的 Logo
🏳️🌈 = $annie->useIllustrator();// Annie 開始製作點陣版本的 Logo
⭐ = $annie->usePhotoshop();// 安珈娜收到設計好的兩個版本的 Logo
$anjana->receiveLogos([🏳️🌈, ⭐]);// 安珈娜確認 Logo,並且感到十分滿意
😍 = $anjana->confirm();
終於在 Annie 的專業設計下,連標準超高的安珈娜也很滿意對這次的 Logo 設計感到滿意,於是就把《給你一點顏色瞧瞧》跟他身邊的創業朋友分享,很快的設計的需求排山倒海而來,Brooky 就忙不過來了,於是他就與每家公司都簽一樣的契約:
甲方:給你一點顏色瞧瞧乙方: XXX 公司乙方公司使用的的商標設計全權由甲方的 『Annie』 來協助規劃與設計。。。。
。。。
關係圖如下:
就這樣子充實的工作生活過了一年,Annie 突然有一天被她的同事 Lara 啓發,也想請個一年的長假去澳洲打工度假,趁年輕的時候多體驗不同的人生,因爲 Brooky 人真的很好,所以也不忍心拒絕 Annie 的請假請求,所以同意了她的假期。
就這樣子 Annie 請了 1 年的長假了,Brooky 等送走了 Annie 回到辦公室的時候,看到疊在桌上的合約的這一行字:
乙方公司使用的的商標設計全權由甲方的 『Annie』 來協助規劃與設計。
這時他才驚覺到:合約上都『寫死』了是委託 Annie 來設計,那 Annie 不在怎麼辦!?
Brooky:「Oops!What Should I Do?」 Brooky 英文很好,情急之下還可以烙英文。
Brooky 突然想起他六度關係那麼遠的有一個朋友傑西好像也是設計師,所以找了傑西來救火,傑西也一口答應全面接手 Annie 以前工作,所以 Brooky 必須把所有的契約上面有 Annie 字樣的全部改成傑西,所以會變成這樣子:
乙方公司使用的的商標設計全權由甲方的 『傑西』 來協助規劃與設計。
關係圖更新如下
才剛好不容易把全部的契約調整完之後,好景不常過了一陣子傑西吃壞肚子烙賽在馬桶上蹲了七七四十九天而無法工作,這時候所有的工作又停擺了,這時候才讓 Brooky 意識到:
契約上面寫死著某人的名字,如果那個人無法工作,那是不是公司的營運都會受到影響了?
所以他回頭想想最一開始他想找的是什麼樣子的員工,或者是更精確的說:這個員工是設計師的話, 他必須具備什麼樣子的技能
?
- 會使用 Photoshop
- 會使用 Illustrator
所以如果會這兩個技能的人的話,如果就可以在《給你一點顏色瞧瞧》擔任設計師?那何不我在契約上全部改寫爲:
乙方公司使用的的商標設計全權由甲方的 『設計師』 來協助規劃與設計。
這樣子只要有具備這兩個技能的人,都可以不管是誰都可以滿足合約上的『甲方的設計師』的這個要求了。
所以關係圖會更新如下:
Brooky 覺得他想到這個方法真是太棒了,所以他制定了『一個設計師必須要有的技能要求』,並且馬上在《404 人力銀行》徵才的頁面寫下這兩個設計師的要求條件:
- 會使用 Photoshop
- 會使用 Illustrator
// 設計師技能介面
interface Designer {
// 使用 Photoshop 設計處理點陣圖檔的技能
public function usePhotoshop(); // 使用 Illustrator 設計處理向量圖檔的技能
public function useIllustrator();
}
凡擔任《給你一點顏色瞧瞧》的設計師的職位的人,不管是誰都要滿足這兩個技能要求,如果沒有滿足的話,就無法勝任設計師的這個職位。
這邊值得注意的是:
設計師技能介面並沒有要求設計師要怎麼使用 Photoshop 與怎麼使用 Illustrator,不管是要用一個圖層設計,或者是用開圖層海完成設計,不管你是不是要用手繪版畫圖,或者是滑鼠畫圖,只要你會用 Photoshop 與 Illustrator 產出設計稿就沒問題了,這也是為什麼沒有 method 的 body 實作區塊。
所以如果 Annie 一年後從澳洲回來了,他又掛上設計師職位的話,他會變成這樣子:
// 首席設計師 Annie 掛上設計師介面技能
class Annie extends Person implements Designer
{
// 使用 Photoshop 設計處理點陣圖檔的技能
public function usePhotoshop()
{
return "使用手繪版操作 PS 設計處理點陣圖檔 ...";
} // 使用 Illustrator 設計處理向量圖檔的技能
public function useIllustrator()
{
return "使用手繪版 AI 設計處理向量圖檔 ...";
}
}
傑西也是設計師,所以他也可以更改爲:
// 次任首席設計師 Jessie 掛上設計師介面技能
class Jessie extends Person implements Designer
{
// 使用 Photoshop 設計處理點陣圖檔的技能
public function usePhotoshop()
{
return "使用滑鼠操作 PS 設計處理點陣圖檔 ...";
} // 使用 Illustrator 設計處理向量圖檔的技能
public function useIllustrator()
{
return "使用滑鼠操作 AI 設計處理向量圖檔 ...";
}
}
這邊可以很明顯的看到兩個人都有因爲實作(implement) 了 Desinger 這個設計師技能介面,所以就必須被要求實作這個介面所定義的兩個 method:`usePhotoshop`、`useIllustrator`,即使 Annie 跟 Jessie 的實作方式不太一樣:Jessie 是用滑鼠操作,Annie 是用手繪版操作,不過一點都不要緊,對於《給你一點顏色瞧瞧》或 Brooky 的角度來說,你只要會:
- 會使用 Photoshop
- 會使用 Illustrator
就足以擔任設計師了。
簡單來說:
介面就像是證照或職位,誰掛了這個證照或職位後誰就要有能力執行這張證照或職位要求的技能。
例如說:如果我們看到醫生,先還沒看到醫生的名稱之前我們就會大概知道他有幫人看診的能力,我們看到廚師,就會知道他有烹飪的能力,看到某人多益考了 900 多分,就可以知道他英文強的不要不要的 😎
所以我們簡單地更新一下 Brooky 的程式碼:
// 創辦人 Brooky
class Brooky extends Person
{
private $company; ....
... // 在 hire 方法裏面定義僱用進來的設計師一定要滿足 Designer 技能介面所要求的技能
public function hire(Designer $designer)
{
$this->company->onboard($designer);
}
}
再來看一下 Brooky 僱用員工的程式碼:
$brooky = new Brooky();$annie = new Annie();
$jessie = new Jessie();// Hello Kitty 沒有實作 Designer 介面(不會使用 Photoshop 與 Illustrator)
$helloKitty = new HelloKitty();// Brooky 僱用 Annie
$brooky->hire($annie); // 錄取 😊// Brooky 僱用 Annie
$brooky->hire($jessie); // 錄取 😊// Brooky 僱用 Hello Kitty
$brooky->hire($helloKitty); // 你雖然很可愛,但是我無法錄取你 😅
interface 的好處
使用 interface 當作一層介面與你的使用端(client)作出隔離,讓你不會出現像上面的例子裡換人(class)就必須替換掉所有的契約(相依的程式碼)以外,也確保持有這張證照的人(實作這個介面的 class)都有滿足這個證照要求技能,在程式的執行上也避免遇到沒有定義此 method 的 bug 出現。
就像是你找了一個有特級廚師證照的小當家他一定可以燒一桌的好菜,因爲他有這個技能才能取得特級廚師的證照,但是你隔壁家的小明因爲他沒有特級廚師的證照,所以就無法執行煮一桌好菜的任務。這也是為什麼上面 Brooky 會無法僱用 Hello Kitty 的原因,因爲 Hello Kitty 沒有去實作設計師的技能介面。
使用時機
說完了為什麼要用 interface 以及好處後,那我們就可以來想想使用的時機是什麼,當你有很多都有某一個功能的 class,但卻有不同的實作方式,例如:鳥、遙控飛機、風箏等等的物件,他們的共通性都是需要『會飛』,但是飛的方式又不盡相同,所以也許就可以置定一個 CanFly 的介面:
interface CanFly {
public function fly();
}
並且把可以飛的東西實作 CanFly 介面
// 鳥
class Bird implements CanFly
{
public function fly()
{
return '拍打翅膀飛起來!';
}
}// 遙控飛機
class RemotePlane implements CanFly
{
public function fly()
{
return '旋轉螺旋槳升起!';
}
}// 風箏
class kite implements CanFly
{
public function fly()
{
return '靠空氣的氣流飛起來!';
}
}// 狗狗(沒有實作 CanFly 介面 😅)
class Dog
{
public function eat()
{
return '吃肉肉';
}
}
假如有個小孩的 class 就可以這樣子寫:
class Kid{
// 小朋友玩會飛的東西
public function play(CanFly $somethingCanFly)
{
$somethingCanFly->fly();
}
}$kid = new Kid();$bird = new Bird();
$kite = new Kite();
$remotePlane = new RemotePlane();
$dog = new Dog();$kid->play($bird); // 飛起來囉
$kid->play($kite); // 飛起來囉
$kid->play($remotePlane); // 飛起來囉
$kid->play($dog); // 狗只會吃肉肉,應該是飛不起來 😅
所以當你在編輯器寫下這一行的時應該就會提示有錯誤的發生:
$kid->play($dog); // 狗只會吃肉肉,應該是飛不起來 😅
其實故事講到這邊其實就差不多可以結束了,如果你不趕時間,就容我最後同場加映『多項介面』。
多項介面
如果繼續沿伸剛剛把介面當作是證照或職位的比喻,例如說:醫生、廚師、設計師、工程師等等,但是如果遇到了前陣子很火紅的『斜槓青年』怎麼辦?有人可能又會設計師的技能也又有工程師的能力,沒錯,那就給他多項介面。
設計師的技能介面:
interface Designer
{
// 使用 Photoshop 設計處理點陣圖檔的技能
public function usePhotoshop(); // 使用 illustrator 設計處理向量圖檔的技能
public function useIllustrator();
}
工程師的技能介面:
// 工程師技能介面
interface Engineer
{
// 寫 coding 能力
public function coding(); // debug 能力
public function debug();
}
所以假如傑西也跑去學寫程式,所以也滿足了工程師的技能介面要求的能力,他的程式碼就可以這樣子調整:
// 實作兩個介面
class Jessie extends Person implements Designer, Engineer
{
// 使用 Photoshop 設計處理點陣圖檔的技能
public function usePhotoshop()
{
return "使用滑鼠操作 PS 設計處理點陣圖檔 ...";
} // 使用 illustrator 設計處理向量圖檔的技能
public function useIllustrator()
{
return "使用滑鼠操作 AI 設計處理向量圖檔 ...";
} public function coding()
{
return "不斷寫有 bug 的 code";
} public function debug()
{
return "不斷 de 自己的寫的 bug";
}
}
呼,今天的故事大概就到這裡囉,這篇有那麼一點長,也很感謝你願意看到這裡囉 😆
一年過後。。
Brooky:「Annie,最近好像很流行 Figma 的樣子,看起來滿好用的,我們要不要之後也導入 Figma?」
Annie:「所以我們公司的設計師都要會使用 Figma?」
Brooky:「好 Figma,不學嗎?」
下集會來聊聊如果突然間《給你一點顏色瞧瞧》對設計師的技能要求(interface)要再新增一個『會使用 Figma』的技能,究竟會對設計師 Annie 與設計師傑西帶來什麼衝擊與影響呢?
是什麼情愛的糾葛與愛恨情仇讓 Brooky 突然看到 Figma 了呢?
我們下集待續,掰。
Icons made by monkik from www.flaticon.com