Clean Architecture Part3 - Design Principle

前導

  • 這裡介紹的 SOLID 是應對 mid-level (module, component)
    • 將遵從 SOILD 的 modules 想成是一塊塊良好的磚頭,即使如此還是有可能建出混亂的建築

SRP

  • 「每個 function 只做一件事」不是 SRP 強調的

  • 每個 module 只為一個,而且只有一個角色而變化

    • 何謂 module?
      • 可能是同一個程式碼檔案
      • 內聚(cohesive)強制綁定程式碼與單一角色
    • 何謂角色?
      • 指的是同一個群體
  • 經由一些症狀察覺違反 SRP

    • 意外重複
      • 不同的角色使用相同的演算法
    • Merge
      • 會有 Merge 產生,就代表有多人修改同一個檔案,這有可能是因為有不同的角色都使用該檔案
  • 要避免違反 SRP 可以依不同角色行為拆分成單獨的 classes

    例如 Employee 中含有 save , calculate_pay , report_hours ,他們都是給不同角色使用

    • 此時可以將這三個 method 拆成不同的 classes,他們都會依賴於 EmployeeData
      • 但要如何追蹤並且讓別人知道這些 classes 是有關連的
        • 使用 Facade Pattern
        • 由 Facade class 進行 delegating 和 instantiating
    • 以 Rails 來說,是比較偏向將 bussiness rule 放在 model 中,也就是 model 包含 Data 和最重要的 functions,那麼可以將這個 model 當成 Facade class
    • 如何避免一個 class 只有一個 function?
      • 一個 class 只有一個「公共」function 是可能的,其中還會有多個 private function 由 public function 使用
  • SRP 原本是關於 functions 和 classes,但此概念也可以出現在以下兩個層級

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
    • 這比較常出現在靜態語言,因為其包含了 importinclude 等等的宣告
    • 靜態語言的解法是讓使用者依賴 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

  • 依賴於抽象而非具體
  • 抽象比具體更穩定