Hi 👋 ~ 今天來跟大家聊聊責任鏈設計模式 (Chain of Responsibility Design Pattern)!
責任鏈設計模式是什麼?
責任鏈模式是一種行爲設計模式。
針對一個處理的請求,在一連串由不同節點組成的流程中,每一個節點可以決定是否進行處理、傳遞給下一個節點或判斷不需要往下處理而駁回請求。
沒有使用責任鏈模式會怎麼樣嗎?🤯 我想大家都有去面試過的經驗,不少公司的面試流程很多都需要有 3 - 4 次面試 🥲
我們這次就用面試流程來看看如何使用責任鏈模式吧!
背景設定 JC KeyCode 是一家新創公司,它需要應徵「軟體工程師」與「設計師」。
針對「軟體工程師」的面試流程是:
HR 電話初步面試
線上技術解題面試
到公司與 CTO 技術解題面試
到公司與 CEO 的 1 on 1 面試
針對「設計師」的面試流程是:
HR 電話初步面試
到公司與設計總監進行設計作品集面試
到公司與 CEO 的 1 on 1 面試
沒有使用責任鏈設計模式的程式碼 我們會先有應徵者的 class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const POSITION_TYPE_ENGINEER = 'enginner' ;const POSITION_TYPE_DESIGNER = 'designer' ;class Interviewee { public string $name ; public string $position ; public int $performance ; public function __construct (string $name , string $position , int $performance ) { $this ->name = $name ; $this ->position = $position ; $this ->performance = $performance ; } }
然後就是 JcKeyCode 這家公司的 class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class JcKeyCode { public function interview (Interviewee $interviewee ): bool { } public function hire (Interviewee $interviewee ) { $position = $interviewee ->position == POSITION_TYPE_ENGINEER ? "工程師" : "設計師" ; echo "{$interviewee->name} - {$position} 面試流程開始: ==================== " . PHP_EOL; $result = $this ->interview ($interviewee ); if ($result ) { $message = "get the offer ! 🎉" ; } else { $message = "interview failed 😭" ; } echo "{$interviewee->name} - {$position} 面試流程結束: {$message} ==================== " . PHP_EOL; echo "" . PHP_EOL; } }
接下來實作 interview
這個 method 的內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public function interview (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance > 5 ) { echo "HR 電話初步面試: Pass" . PHP_EOL; if ($interviewee ->position == POSITION_TYPE_ENGINEER) { echo "線上技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance > 30 ) { echo "線上技術解題面試: Pass" . PHP_EOL; echo "到公司與 CTO 技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance > 70 ) { echo "到公司與 CTO 技術解題面試: Pass" . PHP_EOL; echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance > 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return true ; } else { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } } else { echo "到公司與 CTO 技術解題面試: Failed" . PHP_EOL; return false ; } } else { echo "線上技術解題面試: Failed" . PHP_EOL; return false ; } } if ($interviewee ->position == POSITION_TYPE_DESIGNER) { echo "到公司與設計總監進行設計作品集面試: Start" . PHP_EOL; if ($interviewee ->performance > 80 ) { echo "到公司與設計總監進行設計作品集面試: Pass" . PHP_EOL; echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance > 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return true ; } else { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } } else { echo "到公司與設計總監進行設計作品集面試: Failed" . PHP_EOL; return false ; } } } else { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } }
最後使用的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function main ( ) { $jcKeyCode = new JcKeyCode (); $john = new Interviewee ("John" , POSITION_TYPE_DESIGNER, 3 ); $jcKeyCode ->hire ($john ); $alice = new Interviewee ("Alice" , POSITION_TYPE_DESIGNER, 15 ); $jcKeyCode ->hire ($alice ); $paul = new Interviewee ("Paul" , POSITION_TYPE_DESIGNER, 85 ); $jcKeyCode ->hire ($paul ); $patty = new Interviewee ("Patty" , POSITION_TYPE_DESIGNER, 95 ); $jcKeyCode ->hire ($patty ); $mary = new Interviewee ("Mary" , POSITION_TYPE_ENGINEER, 4 ); $jcKeyCode ->hire ($mary ); $tommy = new Interviewee ("Tommy" , POSITION_TYPE_ENGINEER, 15 ); $jcKeyCode ->hire ($tommy ); $peter = new Interviewee ("Peter" , POSITION_TYPE_ENGINEER, 80 ); $jcKeyCode ->hire ($peter ); $david = new Interviewee ("David" , POSITION_TYPE_ENGINEER, 99 ); $jcKeyCode ->hire ($david ); } main ();
沒有使用責任鏈設計模式的完整程式碼 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 <?php const POSITION_TYPE_ENGINEER = 'enginner' ;const POSITION_TYPE_DESIGNER = 'designer' ;class Interviewee { public string $name ; public string $position ; public int $performance ; public function __construct (string $name , string $position , int $performance ) { $this ->name = $name ; $this ->position = $position ; $this ->performance = $performance ; } } class JcKeyCode { public function interview (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance > 5 ) { echo "HR 電話初步面試: Pass" . PHP_EOL; if ($interviewee ->position == POSITION_TYPE_ENGINEER) { echo "線上技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance > 30 ) { echo "線上技術解題面試: Pass" . PHP_EOL; echo "到公司與 CTO 技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance > 70 ) { echo "到公司與 CTO 技術解題面試: Pass" . PHP_EOL; echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance > 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return true ; } else { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } } else { echo "到公司與 CTO 技術解題面試: Failed" . PHP_EOL; return false ; } } else { echo "線上技術解題面試: Failed" . PHP_EOL; return false ; } } if ($interviewee ->position == POSITION_TYPE_DESIGNER) { echo "到公司與設計總監進行設計作品集面試: Start" . PHP_EOL; if ($interviewee ->performance > 80 ) { echo "到公司與設計總監進行設計作品集面試: Pass" . PHP_EOL; echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance > 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return true ; } else { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } } else { echo "到公司與設計總監進行設計作品集面試: Failed" . PHP_EOL; return false ; } } } else { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } } public function hire (Interviewee $interviewee ) { $position = $interviewee ->position == POSITION_TYPE_ENGINEER ? "工程師" : "設計師" ; echo "{$interviewee->name} - {$position} 面試流程開始: ==================== " . PHP_EOL; $result = $this ->interview ($interviewee ); if ($result ) { $message = "get the offer ! 🎉" ; } else { $message = "interview failed 😭" ; } echo "{$interviewee->name} - {$position} 面試流程結束: {$message} ==================== " . PHP_EOL; echo "" . PHP_EOL; } } function main ( ) { $jcKeyCode = new JcKeyCode (); $john = new Interviewee ("John" , POSITION_TYPE_DESIGNER, 3 ); $jcKeyCode ->hire ($john ); $alice = new Interviewee ("Alice" , POSITION_TYPE_DESIGNER, 15 ); $jcKeyCode ->hire ($alice ); $paul = new Interviewee ("Paul" , POSITION_TYPE_DESIGNER, 85 ); $jcKeyCode ->hire ($paul ); $patty = new Interviewee ("Patty" , POSITION_TYPE_DESIGNER, 95 ); $jcKeyCode ->hire ($patty ); $mary = new Interviewee ("Mary" , POSITION_TYPE_ENGINEER, 4 ); $jcKeyCode ->hire ($mary ); $tommy = new Interviewee ("Tommy" , POSITION_TYPE_ENGINEER, 15 ); $jcKeyCode ->hire ($tommy ); $peter = new Interviewee ("Peter" , POSITION_TYPE_ENGINEER, 80 ); $jcKeyCode ->hire ($peter ); $david = new Interviewee ("David" , POSITION_TYPE_ENGINEER, 99 ); $jcKeyCode ->hire ($david ); } main ();John - 設計師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Failed John - 設計師 面試流程結束: interview failed 😭 ==================== Alice - 設計師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 到公司與設計總監進行設計作品集面試: Start 到公司與設計總監進行設計作品集面試: Failed Alice - 設計師 面試流程結束: interview failed 😭 ==================== Paul - 設計師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 到公司與設計總監進行設計作品集面試: Start 到公司與設計總監進行設計作品集面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Failed Paul - 設計師 面試流程結束: interview failed 😭 ==================== Patty - 設計師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 到公司與設計總監進行設計作品集面試: Start 到公司與設計總監進行設計作品集面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Pass Patty - 設計師 面試流程結束: get the offer ! 🎉 ==================== Mary - 工程師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Failed Mary - 工程師 面試流程結束: interview failed 😭 ==================== Tommy - 工程師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 線上技術解題面試: Start 線上技術解題面試: Failed Tommy - 工程師 面試流程結束: interview failed 😭 ==================== Peter - 工程師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 線上技術解題面試: Start 線上技術解題面試: Pass 到公司與 CTO 技術解題面試: Start 到公司與 CTO 技術解題面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Failed Peter - 工程師 面試流程結束: interview failed 😭 ==================== David - 工程師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 線上技術解題面試: Start 線上技術解題面試: Pass 到公司與 CTO 技術解題面試: Start 到公司與 CTO 技術解題面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Pass David - 工程師 面試流程結束: get the offer ! 🎉 ====================
🤯 上述程式碼的問題:
在 interveiw()
裏面 if
跟 else
的複雜的判斷邏輯混雜在一起
在 interveiw()
裏面的判斷式無法共用,例如 engineer
跟 designer
最後都需要經過 CEO 的面試,但卻要因爲不同的狀況要分別寫在兩處
在 interveiw()
裏面已經這麼混亂的情況下,如果要再新增一個 HR
的職位面試判斷,想必是一場災難。。。 🤯
例如: HR 只需要給 CEO 面試過關即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public function interview (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance > 5 ) { echo "HR 電話初步面試: Pass" . PHP_EOL; if ($interviewee ->position == POSITION_TYPE_ENGINEER) { } if ($interviewee ->position == POSITION_TYPE_DESIGNER) { } if ($interviewee ->position == POSITION_TYPE_HR) { echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance > 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return true ; } else { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } } } else { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } }
相信你看到上述的實作應該覺得有點不妙。。。 🥲
那些下來我們就來看看如何使用責任鏈設計模式吧!
責任鏈設計模式組成
成員
用途
Handler (interface)
* setNext(h Handler)* handle(r Request)
Base Handler
* setNext(h Handler)
* handle(r Request)
Concrete Handler
* setNext(h Handler)* handle(r Request)
使用責任鏈設計模式的程式碼調整 我們把 Interview
設定爲 Handler interface
1 2 3 4 5 6 interface Interview { public function handle (Interviewee $interviewee ): bool ; public function setNext (Interview $interview ) ; }
建立 Base Handler 把每一個 Handler 都需要有 setNext()
跟 passToNext()
,剩下 handle()
給繼承的類別自己實作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 abstract class BaseInterview implements Interview { protected Interview $next ; public function setNext (Interview $interview ) { $this ->next = $interview ; } abstract public function handle (Interviewee $interviewee ): bool ; protected function passToNext (Interviewee $interviewee ): bool { if (isset ($this ->next)) { return $this ->next->handle ($interviewee ); } return true ; } }
接下來把需要的面試實作出來,分別有:
HR 電話初步面試
線上技術解題面試
到公司與 CTO 技術解題面試
到公司與設計總監進行設計作品集面試
到公司與 CEO 的 1 on 1 面試
HR 電話初步面試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class HRPhoneCallInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance <= 5 ) { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } echo "HR 電話初步面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } }
線上技術解題面試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class TechicalOnLineInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "線上技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance <= 30 ) { echo "線上技術解題面試: Failed" . PHP_EOL; return false ; } echo "線上技術解題面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } }
到公司與 CTO 技術解題面試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class OnSiteTechicalWithCTOInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與 CTO 技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance <= 70 ) { echo "到公司與 CTO 技術解題面試: Failed" . PHP_EOL; return false ; } echo "到公司與 CTO 技術解題面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } }
到公司與設計總監進行設計作品集面試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class OnSiteDesignSupervisorInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與設計總監進行設計作品集面試: Start" . PHP_EOL; if ($interviewee ->performance <= 80 ) { echo "到公司與設計總監進行設計作品集面試: Failed" . PHP_EOL; return false ; } echo "到公司與設計總監進行設計作品集面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } }
到公司與 CEO 的 1 on 1 面試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class OnSiteFinalCEOInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance <= 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } }
呼~喘口氣~ 😀
接下來我們就在 JcKeyCode
類別裡新增「設定工程師面試流程」的 method: prepareForEngineerInterview
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private function prepareForEngineerInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $techicalOnLineInterview = new OnSiteDesignSupervisorInterview (); $onSiteTechicalWithCTOInterview = new OnSiteTechicalWithCTOInterview (); $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($techicalOnLineInterview ); $techicalOnLineInterview ->setNext ($onSiteTechicalWithCTOInterview ); $onSiteTechicalWithCTOInterview ->setNext ($onSiteFinalCEOInterview ); return $hrPhoneCallInterview ; }
在 JcKeyCode
類別裡新增「設定設計師面試流程」的 method: prepareForDesignerInterview
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private function prepareForDesignerInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $onSiteDesignSupervisorInterview = new OnSiteDesignSupervisorInterview (); $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($onSiteDesignSupervisorInterview ); $onSiteDesignSupervisorInterview ->setNext ($onSiteFinalCEOInterview ); return $hrPhoneCallInterview ; }
接著調整 JcKeyCode
的 interview()
爲以下,就大功告成囉!
1 2 3 4 5 6 7 8 9 10 11 public function interview (Interviewee $interviewee ): bool { switch ($interviewee ->position){ case POSITION_TYPE_ENGINEER: return $this ->prepareForEngineerInterview ()->handle ($interviewee ); case POSITION_TYPE_DESIGNER: return $this ->prepareForDesignerInterview ()->handle ($interviewee ); default : throw new \Exception ("Position Not Available!" ); } }
使用責任鏈設計模式的完整程式碼 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 <?php const POSITION_TYPE_ENGINEER = 'enginner' ;const POSITION_TYPE_DESIGNER = 'designer' ;class Interviewee { public string $name ; public string $position ; public int $performance ; public function __construct (string $name , string $position , int $performance ) { $this ->name = $name ; $this ->position = $position ; $this ->performance = $performance ; } } interface Interview { public function handle (Interviewee $interviewee ): bool ; } abstract class BaseInterview implements Interview { protected Interview $next ; public function setNext (Interview $interview ) { $this ->next = $interview ; } abstract public function handle (Interviewee $interviewee ): bool ; protected function passToNext (Interviewee $interviewee ): bool { if (isset ($this ->next)) { return $this ->next->handle ($interviewee ); } return true ; } } class HRPhoneCallInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance <= 5 ) { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } echo "HR 電話初步面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } } class TechicalOnLineInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "線上技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance <= 30 ) { echo "線上技術解題面試: Failed" . PHP_EOL; return false ; } echo "線上技術解題面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } } class OnSiteTechicalWithCTOInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與 CTO 技術解題面試: Start" . PHP_EOL; if ($interviewee ->performance <= 70 ) { echo "到公司與 CTO 技術解題面試: Failed" . PHP_EOL; return false ; } echo "到公司與 CTO 技術解題面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } } class OnSiteDesignSupervisorInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與設計總監進行設計作品集面試: Start" . PHP_EOL; if ($interviewee ->performance <= 80 ) { echo "到公司與設計總監進行設計作品集面試: Failed" . PHP_EOL; return false ; } echo "到公司與設計總監進行設計作品集面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } } class OnSiteFinalCEOInterview extends BaseInterview { public function handle (Interviewee $interviewee ): bool { echo "到公司與 CEO 的 1 on 1 面試: Start" . PHP_EOL; if ($interviewee ->performance <= 90 ) { echo "到公司與 CEO 的 1 on 1 面試: Failed" . PHP_EOL; return false ; } echo "到公司與 CEO 的 1 on 1 面試: Pass" . PHP_EOL; return $this ->passToNext ($interviewee ); } } class JcKeyCode { private function prepareForEngineerInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $techicalOnLineInterview = new OnSiteDesignSupervisorInterview (); $onSiteTechicalWithCTOInterview = new OnSiteTechicalWithCTOInterview (); $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($techicalOnLineInterview ); $techicalOnLineInterview ->setNext ($onSiteTechicalWithCTOInterview ); $onSiteTechicalWithCTOInterview ->setNext ($onSiteFinalCEOInterview ); return $hrPhoneCallInterview ; } private function prepareForDesignerInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $onSiteDesignSupervisorInterview = new OnSiteDesignSupervisorInterview (); $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($onSiteDesignSupervisorInterview ); $onSiteDesignSupervisorInterview ->setNext ($onSiteFinalCEOInterview ); return $hrPhoneCallInterview ; } public function interview (Interviewee $interviewee ): bool { switch ($interviewee ->position){ case POSITION_TYPE_ENGINEER: return $this ->prepareForEngineerInterview ()->handle ($interviewee ); case POSITION_TYPE_DESIGNER: return $this ->prepareForDesignerInterview ()->handle ($interviewee ); default : throw new \Exception ("Position Not Avaiable!" ); } } public function hire (Interviewee $interviewee ) { $position = $interviewee ->position == POSITION_TYPE_ENGINEER ? "工程師" : "設計師" ; echo "{$interviewee->name} - {$position} 面試流程開始: ==================== " . PHP_EOL; $result = $this ->interview ($interviewee ); if ($result ) { $message = "get the offer ! 🎉" ; } else { $message = "interview failed 😭" ; } echo "{$interviewee->name} - {$position} 面試流程結束: {$message} ==================== " . PHP_EOL; echo "" . PHP_EOL; } } function main ( ) { $jcKeyCode = new JcKeyCode (); $john = new Interviewee ("John" , POSITION_TYPE_DESIGNER, 3 ); $jcKeyCode ->hire ($john ); $alice = new Interviewee ("Alice" , POSITION_TYPE_DESIGNER, 15 ); $jcKeyCode ->hire ($alice ); $paul = new Interviewee ("Paul" , POSITION_TYPE_DESIGNER, 85 ); $jcKeyCode ->hire ($paul ); $patty = new Interviewee ("Patty" , POSITION_TYPE_DESIGNER, 95 ); $jcKeyCode ->hire ($patty ); $mary = new Interviewee ("Mary" , POSITION_TYPE_ENGINEER, 4 ); $jcKeyCode ->hire ($mary ); $tommy = new Interviewee ("Tommy" , POSITION_TYPE_ENGINEER, 15 ); $jcKeyCode ->hire ($tommy ); $peter = new Interviewee ("Peter" , POSITION_TYPE_ENGINEER, 80 ); $jcKeyCode ->hire ($peter ); $david = new Interviewee ("David" , POSITION_TYPE_ENGINEER, 99 ); $jcKeyCode ->hire ($david ); } main ();
好的!終於調整完了~ 我想最大的改變就是 interview
method 吧?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public function interview (Interviewee $interviewee ): bool { echo "HR 電話初步面試: Start" . PHP_EOL; if ($interviewee ->performance > 5 ) { echo "HR 電話初步面試: Pass" . PHP_EOL; if ($interviewee ->position == POSITION_TYPE_ENGINEER) { } if ($interviewee ->position == POSITION_TYPE_DESIGNER) { } } else { echo "HR 電話初步面試: Failed" . PHP_EOL; return false ; } } public function interview (Interviewee $interviewee ): bool { switch ($interviewee ->position){ case POSITION_TYPE_ENGINEER: return $this ->prepareForEngineerInterview ()->handle ($interviewee ); case POSITION_TYPE_DESIGNER: return $this ->prepareForDesignerInterview ()->handle ($interviewee ); default : throw new \Exception ("Position Not Available!" ); } }
不過調整這樣有什麼好處呢? 😅
新增需求的調整容易 除了把雜亂的 if-else
基於 單一職責原則 放在個別的 Handler 去處理以外,對於上述新增 HR 面試流程的需求,也可以很輕易地調整。我們只需要在 JcKeyCode
類別裡新增「設定 HR 面試流程」的 method: prepareForHRInterview
1 2 3 4 5 6 7 8 9 10 11 private function prepareForHRInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($onSiteFinalCEOInterview ); return $hrPhoneCallInterview ; }
然後只需要在 interview()
上加上 HR 的判斷即可囉!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public function interview (Interviewee $interviewee ): bool { switch ($interviewee ->position){ case POSITION_TYPE_ENGINEER: return $this ->prepareForEngineerInterview ()->handle ($interviewee ); case POSITION_TYPE_DESIGNER: return $this ->prepareForDesignerInterview ()->handle ($interviewee ); case POSITION_TYPE_HR: return $this ->prepareForHRInterview ()->handle ($interviewee ); default : throw new \Exception ("Position Not Available!" ); } } Amy - 人資 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Pass Amy - 人資 面試流程結束: get the offer ! 🎉 ====================
調整組合容易 如果今天想要應徵的工程師也要會點設計的技能,所以在工程師的面試流程中,需要在「到公司與 CTO 技術解題面試」與「到公司與 CEO 的 1 on 1 面試」之間,再多一關「到公司與設計總監進行設計作品集面試」的面試,我們只需要調整 prepareForEngineerInterview
即可~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private function prepareForEngineerInterview ( ): Interview { $hrPhoneCallInterview = new HRPhoneCallInterview (); $techicalOnLineInterview = new OnSiteDesignSupervisorInterview (); $onSiteTechicalWithCTOInterview = new OnSiteTechicalWithCTOInterview (); $onSiteDesignSupervisorInterview = new OnSiteDesignSupervisorInterview (); ⬅️ modified $onSiteFinalCEOInterview = new OnSiteFinalCEOInterview (); $hrPhoneCallInterview ->setNext ($techicalOnLineInterview ); $techicalOnLineInterview ->setNext ($onSiteTechicalWithCTOInterview ); $onSiteTechicalWithCTOInterview ->setNext ($onSiteDesignSupervisorInterview ); ⬅️ modified $onSiteDesignSupervisorInterview ->setNext ($onSiteFinalCEOInterview ); ⬅️ modified return $hrPhoneCallInterview ; } David - 工程師 面試流程開始: ==================== HR 電話初步面試: Start HR 電話初步面試: Pass 到公司與設計總監進行設計作品集面試: Start 到公司與設計總監進行設計作品集面試: Pass 到公司與 CTO 技術解題面試: Start 到公司與 CTO 技術解題面試: Pass 到公司與設計總監進行設計作品集面試: Start 到公司與設計總監進行設計作品集面試: Pass 到公司與 CEO 的 1 on 1 面試: Start 到公司與 CEO 的 1 on 1 面試: Pass David - 工程師 面試流程結束: get the offer ! 🎉 ====================
是不是方便許多呢? 😀
責任鏈設計模式優缺點 優點
🎉 單一職責原則把不同的驗證放在各自的類別裏面
🎉 驗證的類別被拆開了,因此更方便調整組合順序與新增組合
🎉 若需要增加額外的驗證,只需最小幅度調整即可,例如上述的 HR 面試流程
缺點
責任鏈設計模式使用時機
如果一個需要經過好幾段檢驗流程的行爲,那也許就會是責任鏈設計模式適合的場景。
總結 以上就是責任鏈設計模式的介紹,感謝你的收看~!
參考