Clean Architecture Part3 - Design Principle
前導
- 這裡介紹的 SOLID 是應對 mid-level (module, component)
- 將遵從 SOILD 的 modules 想成是一塊塊良好的磚頭,即使如此還是有可能建出混亂的建築
SRP
-
「每個 function 只做一件事」不是 SRP 強調的
-
每個 module 只為一個,而且只有一個角色而變化
- 何謂 module?
- 可能是同一個程式碼檔案
- 內聚(cohesive)強制綁定程式碼與單一角色
- 何謂角色?
- 指的是同一個群體
- 何謂 module?
-
經由一些症狀察覺違反 SRP
- 意外重複
- 不同的角色使用相同的演算法
- Merge
- 會有 Merge 產生,就代表有多人修改同一個檔案,這有可能是因為有不同的角色都使用該檔案
- 意外重複
-
要避免違反 SRP 可以依不同角色行為拆分成單獨的 classes
例如 Employee 中含有
save
,calculate_pay
,report_hours
,他們都是給不同角色使用- 此時可以將這三個 method 拆成不同的 classes,他們都會依賴於
EmployeeData
- 但要如何追蹤並且讓別人知道這些 classes 是有關連的
- 使用 Facade Pattern
- 由 Facade class 進行 delegating 和 instantiating
- 但要如何追蹤並且讓別人知道這些 classes 是有關連的
- 以 Rails 來說,是比較偏向將 bussiness rule 放在 model 中,也就是 model 包含 Data 和最重要的 functions,那麼可以將這個 model 當成 Facade class
- 如何避免一個 class 只有一個 function?
- 一個 class 只有一個「公共」function 是可能的,其中還會有多個 private function 由 public function 使用
- 此時可以將這三個 method 拆成不同的 classes,他們都會依賴於
-
SRP 原本是關於 functions 和 classes,但此概念也可以出現在以下兩個層級
- Component
- Architecture
- facade parttern
OCP
- 系統的行為應是擴充而非修改,因修改容易導致錯誤
- 大部分的人覺得這個 Principle 是對於 module 或 class 的設計規範,但他也可以用於系統層面
- 一個好的架構在實作新需求時,應盡可能將舊扣的變更降低至 0
- 如何做到?
- 遵從 SRP,讓事情的變動原因變得單一
- 管理好依賴關係
- 確保單向依賴
- 最終會得出一個 Root,以 Rails 來說應是其中一個 Model
- 依賴於 interface 而非具體物件(靜態語言)
- 如何做到?
interface Presenter { public void present(); } class Controller { void call(Presenter presenter){} } class ScreenPresenter implements Presenter { public void present(){} }
LSP
- 主要用來規範繼承,但也可以用來評估 interface 和 implementation
- 只要容忍些微的違反,就會導致整個系統充滿額外的判斷機制
ISP
- 當使用者直接使用 object 的 method,並且各個使用者皆使用不完全相同的 methods 時,這些 methods 會產生無可避免地相依,這些相依會讓整個 class rebuild 或是 redeploy
- 這比較常出現在靜態語言,因為其包含了
import
或include
等等的宣告 - 靜態語言的解法是讓使用者依賴 interface,而 object 則是實做這些 interface
- 但動態語言沒有這個問題
- 這比較常出現在靜態語言,因為其包含了
- 當依賴於一個 module 其擁有太多你不需要的 functions 時,就容易受到牽連
- 在 component cohesion 裡會有更詳細的介紹
範例:由於 User1 ~3 需要
import Op
導致就算只變更Op
裡其中一個 method,也會讓 User1 ~ 3 重新編譯
class Op { public void op1() {}; public void op2() {}; public void op3() {}; } class User1 { public void doSomething(Op arg) { arg.op1(); } } class User2 { public void doSomething(Op arg) { arg.op2(); } } class User3 { public void doSomething(Op arg) { arg.op3(); } }
若是使用 interface 就不會有此問題
class Op implements Op1, Op2, Op3 {}; interface Op1 { public void op1(); } interface Op2 { public void op2(); } interface Op2 { public void op2(); } class User1 { public void doSomething(Op1 arg) { arg.op1(); } } class User2 { public void doSomething(Op1 arg) { arg.op2(); } } class User3 { public void doSomething(Op1 arg) { arg.op3(); } }
DIP
- 依賴於抽象而非具體
- 抽象比具體更穩定