--- tags: Clean Architecture,SOLID title: Chapter 7-11 --- {%hackmd Sy5Quc5JY %} # <font color="#F59F00">**SOLID 設計原則**</font> :::info **SOLID只是一個想法,而Design Pattern是一個讓這個想法實踐的手段** ::: :::info **DS: DataStructer DB: DataBase I: Interface 開放的箭頭: using 關係(import DB, 使用 Type class) 封閉的箭頭: 實作或繼承** ::: ## <font color="#F59F00">**SRP: Single Responsibility Prcinple(單一職責原則)**</font> * <font color="#92d1ff">**Definition**</font> <br> >**A module should have one, and only have one reason to change.** **一個類別(class)只做一件事,如果超過一件事就要把他拆成小類別。** * **Example** <br> ```java public class AuthManager { public void login(String userName, String userPassward) { //... } public void logout() { //... } } ``` 你可以說 AuthManager 違反了 SRP,因為他做了兩件事 但我也可以說,他只做了一件事,就是負責 Handle user's authentication 因此有了下面的另一個定義 <br> >**A module should be responsible to,one and only one user or stakeholder.** **一個類別(class)只對一個角色負責。** * **Example** <br> ```java public class Empolyee { public int calculatePay() { //... } public ReportHours reportHours() { //... } public void save() { //... } } ``` <span style="display:block;text-align:center;"><img src="https://i.imgur.com/qgPTY1T.png" width="600" height="400"></img></span> 這個類別違反了 SRP * **Def-1: 它有 3 個 reasons to change** * 如果會計部更改時薪,更改 `calculatePay()` * 如果人事部更改加班,更改 `reportHours()` * 如果工程師更改資料,更改 `save()` * **Def-2: 它負責了 3 個 users** * `calculatePay()` 由會計部負責->CFO * `reportHours()` 由人事部負責->COO * `save()` 工程師負責->CTO Q : 回顧 AuthManager 是否違反 SRP ? A : 不一定,根據實作決定。 * **Example** <br> ```java public class AuthManager { private String userName = ""; public void logIn(String userName, String userPassword) { if(userName.compareTo("hyi1014")&&userPassword.compareTo("1234")) { this.userName = userName; } if(userName.compareTo("jessica0505")&&userPassword.compareTo("4321")) { this.userName = userName; } } public void logout() { userName = ""; } } ``` Q : 這是一個很爛的code,但如果你真的這麼寫了,那要怎麼改變的理由呢? A : 1. 改變密碼, 2. Add/Remove users 但仔細想一想,這兩件對你的實作來說是同一件事,只需要改變驗證的方法,也就是 `if` ,所以以上實作只有一個理由改變,符合 SRP 。讓我們再看一個範例。 * **Example** <br> ```java public class AuthManager { private String userName = ""; public void logIn(String userName, String userPassword) { String hash = hashPassword(userPassword); if(isUserInDB(userName,hash)) { this.userName = userName; } } public void logout() { userName = ""; } public String hashPassword(String Userpassword) { //hash alogrithm } public boolean isUserInDB(String userName, String hash) { //call DB } } ``` Q : 那這個code有什麼理由改變呢? A : 1. 改變 hash 演算法, 2. 改變底層DataBase的時候 兩個reason to change , 所以違反了SRP。 Q : 那要怎麼更改 AuthManager呢? A : ```java public class AuthManger { private String userName = ""; private PasswordHasher passwordHasher; private DBChecker databaseChecker; public void logIn(String userName, String userPassword) { String hash = passwordHasher.hashPassword(userPassword); if(databaseChecker.isUserInDB(userName, hash)) { this.userName = userName; } } public void logOut() { userName = ""; } } public class PasswordHasher { public String hashPassword(String userPassword) { //hash alogrithm } } public class DBChecker { public isUserInDB(String userName, String hash) { //... } } ``` <span style="display:block;text-align:center;"><img src="https://i.imgur.com/HJ90mhD.png" width="600" height="400"></img></span> * <font color="#92d1ff">**Problems**</font> * **Example** 今天人資部跟你說,原本工作超過8小時算加班,現在要工作超過10小時才算加班 請幫我改變一下 `reportHours()` 的產生方式,然後你就改了 `reportHours()` 的實作,到了月底發薪日你覺得奇怪,你的工時沒有超過8小時,但為什麼你拿的錢變多了呢,然後公司CFO打給你,說公司虧了好幾百萬,你驚覺慘了,再仔細看你的程式,你發現 ```java public class Empolyee { public int calculatePay() { //... ReportHours RH = new reportHours(); //... } public ReportHours reportHours() { //... } public void save() { //... } } ``` 結果你發現,`calculatePay()` 在呼叫 `reportHours()` 影響了會計部的舊功能。這是因為函式與函式之間過度耦合(couple),所以一個更改就會產生很多副作用(side effect)。 * <font color="#92d1ff">**Resolution**</font> 把未來有可能更改的函式分離到其他類別(class),一個常見的解決手段是用 FACADE mode。 ```java public class EmpolyeeFacade { private String id; public string getId(){return id;} PayCalculator payCalculator; HoursReporter hoursReporter; EmployeeSaver employeeSaver; public EmployeeFacade( String id, PayCalculator payCalculator, HoursReporter hoursReporter, EmployeeSaver employeeSaver) { this.id = id; this.payCalculator = payCalculator; this.hoursReporter = hoursReporter; this.employeeSaver = employeeSaver; } public void employee() { System.out.println("Employee %s", id); payCalculator.calculatePay(); hoursReporter.reportHours(); employeeSaver.save(); } } public class PayCalculator { public int calculatePay() { //... } } public class HoursReporter { public ReportHours reportHours() { //... } } class EmployeeSaver { public void save() { //... } } ``` 呼叫 ```java EmployeeFacade employee = new emEmployeeFacade(id, payCalculator, hoursReporter,employeeSaver); employee.employee(); ``` <span style="display:block;text-align:center;"><img src="https://i.imgur.com/qXzRh9u.png" width="700" height="450"></img></span> 你可以能會覺得,每個類別只包含了一個函式,但並非如此,每個類別都包含了許多 private 函式。 * <font color="#92d1ff">**Conclusion**</font> 符不符合 SRP ,取決於你對於你商業邏輯的了解,沒有一定的標準答案,而 SRP 的目的在於,在已知的設計目標下,將功能分割成一個類別只做一件事(一個類別只對一個腳色負責),以達到最大的修改彈性。 <br> :::warning **SRP 是在函式和類別層面的,而在元件層面(兩個層次以上),它成為 CCP** ::: ## <font color="#F59F00">**OCP: Open-Close Prcinple(開放-封閉原則)**</font> * <font color="#92d1ff">**Definition**</font> >**A software entities(classes, modules, functions, etc) should be open for extension, but closed for modification.** **一個軟體應該對擴展是開放的,對修改則是封閉的, 這句話我們下面再解釋,先把上述記在心裡,我們來聽聽其他定義。** >**Depend on stable abstractions and modify system's behavior by providing differen realization(Concrete object).** >**Protected Variation pattern: Identify points of predicted variation and create a stable interface around them** **上述兩個定義,簡而言之,建立一層抽象,並用層抽象(abstraction, interface)去建立實像。** * **Example** <br> ```java public class SalaryCalculator { public int calculateSalary(Employee employee) { //... int taxDedection = new calculateTax(employee); //... } private int calculateTax(Employee employee) { switch(employee.getType()) { case FullTime: //.. case Contractor: //.. } } } ``` 以上違反了SRP,我們做一點修改。 ```java public class SalaryCalculator { private TaxCalculator taxCalculator; public int calculateSalary(Employee employee) { int taxDection = taxCalculator.calculateTax(employee); } } public class TaxCalculator { public int calculateTax(Employee employee) { switch(employee.getType()) { case FullTime: //.. case Contractor: //.. } } } ``` * <font color="#92d1ff">**Problems & Soultions**</font> Q : 現在 SalaryCalculator 沒有問題了,但是 TaxCalcalator 卻有很大的問題,如果要新增類型,必須不斷的增加 `switch`,那我們要怎麼辦呢? A : 在 TaxCalcalator 上面加上一層抽象層,讓 TaxCalculator 變成一個介面,用這個介面去實作類別。使用抽象 interface 把實作留給 subclass ,這個手段為 Strategy mode。 ```java public class SalaryCalculator { private TaxCalculator taxcalculator; public int calculateSalary(Employee employee) { //... switch(employee.getType()) { case FullTime: //.. case Contractor: //.. } } } public interface TaxCalculator { public int calculateTax(Employee employee); } public class TaxCalculator_FullTime implements TaxCalculator { calculateTax(Employee employee) { //calculate FullTime tax } } public class TaxCalculator_Contractor implements TaxCalculator { calculateTax(Employee employee) { //calculate Contractor tax } } ``` Q : 你說奇怪,你只是把 `switch` 從 TaxCalculator 移到 SalaryCalculator 這有什麼用嗎? A : 有用!因為讓 `switch` 裡面變成 simple Factory ,運用這個手段,可以讓 SalaryCalculator 變成 ```java public class SalaryCalculator { private TaxCalculatorFactory taxcalculatorFactory; public int calculateSalary(Employee employee) { //... TaxCalculator taxCalculatorFactory = taxCalculatorFactory.newCalculator(employee.getType()); { int taxDection = taxCalculator.calculator(employee); //... } } } public class TaxCalculatorFactory { public TaxCalculator newCalculator(Employee employee) { switch (employeeType) { case FULL_TIME: return new TaxCalculatorFullTime(); case CONTRACTOR: return new TaxCalculatorContractor(); default: return new TaxCalculatorDefault(); } } public interface TaxCalculator { public int calculateTax(Employee employee); } public class TaxCalculator_FullTime implements TaxCalculator { calculateTax(Employee employee) { //calculate FullTime tax } } public class TaxCalculator_Contractor implements TaxCalculator { calculateTax(Employee employee) { //calculate Contractor tax } } ``` <span style="display:block;text-align:center;"><img src="https://i.imgur.com/ZkPvy0H.png" width="750" height="500"></img></span> Q : 如果現在要新增一個 TaxCalculator 的類型, TaxCalculator_PartTime? A : 只要讓 TaxCalculator extend TaxCalculator 然後在 TaxCalculator_PartTime 裡面執行 partTime 的 calculateTax 就好了。 <span style="display:block;text-align:center;"><img src="https://i.imgur.com/DX45mco.png" width="750" height="500"></img></span> * <font color="#92d1ff">**Review Definition**</font> >**Depend on stable abstractions and modify system's behavior by providing differen realization(Concrete object).** **TaxCalculator 就是 stable abstractions , TaxCalculatorFullTime, TaxCalculatorContrator 就是 different realizations。** >**Protected Variation pattern: Identify points of predicted variation and create a stable interface around them** **predicted variation就是稅金計算的方式 而 stable interface 就是 TaxCalculator。** >**A software entities(classes, modules, functions, etc) should be open for extension, but closed for modification.** **close 是要有一個不改變的抽象層,open 是要能提供彈性的實作。** * <font color="#92d1ff">**Conclusion**</font> 一個恰當的抽象可以讓你的程式更有彈性,而 Simple Factory 和 Strategy mode 只是讓我們完成OCP的手段。 * <font color="#92d1ff">**Assumption**</font> 如果我們用 SRP OCP 來妥善組織一個財務摘要的系統,然後用 DIP(之後解釋) 建立之間的依賴關係。將處理程序劃分在類別中,並將類別分離到元件(較大的階層,之後解釋)中 <br> 在元件階層的依賴關係 <br> <span style="display:block;text-align:center;"><img src="https://i.imgur.com/7Fc9fR2.png" width="750" height="500"></img></span> **如果要保護元件 A 免於元件 B 的改變影響,那麼元件 B 應該依賴元件 A (B->A)** 我們希望保護 Interactor 免於 Controller 的改變、Interactor 免於 DataBase 的改變、 Controller 免於 Presenter 的改變、Presenter 免於 Veiw 的改變。 而 Interactor 是最符合 OCP 的位置,免於其他元件的改變影響。 Q : 為什麼要這樣設計呢? A : 因為 Interactor 裡面包含了 **!業務規則!**,建立保護階層,是基於層級的觀念,有就是說, Interactor 是最高階層,最高指導原則。 <br> 元件與類別的依賴關係 <br> <span style="display:block;text-align:center;"><img src="https://i.imgur.com/26nJLL1.png" width="750" height="500"></img></span> Q : 為什麼要這樣設計呢? A : 圖中大部分的複雜性都是為了 **改變方向**,確保元件之間有正確的依賴方向。 例如 : Financial Data Gate 介面,這是為了反向從 Interactor 元件指向 Database 元件的依賴關係,而 Financial Report Representer 和 View 介面也是如此。 Financial Report Requester 介面卻有所不同,是為了保護 Financial Report Controller 知道 Interactor 太多資訊。 Q : 如果沒有 Financial Report Requester 介面會怎樣? A : Financial Report Controller 會對 Financial entities 有間接的傳遞,得知 Interactor 內更多資訊,傳遞性依賴,違反了 ISP 原則和 CRP 原則(之後會解釋),**軟體不應該依賴他們使用不到的東西。** 因此我們的首要任務是保護 Interactor 不受到 Controller 的更改影響,我們也希望,透過隱藏 Interactor 內部,保護 Controller 受到 Interactor 的更改影響。 ## <font color="#F59F00">**LSP: Liskov Sustitution Principle(Liskov 替換原則)**</font> * <font color="#92d1ff">**Definition**</font> >**If S is a SubType of T, then objects of type of type T may be replaced with objects of type S without altering any of the desirable properities of the program.** **如果程式碼中的類別 T 物件,都可以被 S 類別給取代,且程式碼還運作正常,那稱 S 為 T 的 subType。** * <font color="#92d1ff">**SubType vs SubClass**</font> * <font color="#98c37a">**SubType**</font> 如同 SLP 原則所說,如果 S 是 T 的 subType 那在所有 T 出現的地方都可以用 S 取代, ==**目標是讓你的架構更彈性。**== * <font color="#98c37a">**SubClass**</font> A extend B, 那你就可以說 B 是 A 的 subClass , ==**目標是 code reuse。**== 當然 subType 也是靠 extend 來達成的,但正確的繼承才有資格被稱為 subType,換句話說,不正確的繼承只能被稱為繼承,而不是 subType。 >**SubType = SubClass(realization) which can be substituted for the type it extends(implements)** * **Example** 如果 S 是 T 的 SubType <br> ```java class Sample { public void foo(T t) { //使用 t 的成員 } public void bar(S s) { //使用 s 的成員 } Sample sample = new Sample(); //Access 因為 s 可以當作 t 使用 sample.foo(s); //Wrong 因為 t 未必有 s 所有的成員 sample.bar(t); } ``` * **Example** 鳥和鴕鳥 <br> ```java class Bird { public void fly() { //fly } } class Ostrich extends Bird { @override public void fly() { //Cannot fly throw new RuntimeException(); } } ``` 在這個例子中 Ostrich 是 Bird 的 SubClass 但不是 Bird 的 SubType 因為不是所有有 Bird 出現的地方都可以用 Ostrich 代替。 <br> ```java Bird b = getBird(); b.fly(); ``` 如果這邊的 Bird 改成 Ostrich 就會有 Exception 跑出來。 * <font color="#92d1ff">**Problems & Resolutions**</font> * **Example** 長方形與正方形,因為正方形是長方形的長寬相等的特例,所以我們讓正方形繼承長方形 <br> ```java class Rectangle { int width; int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } } class Square extends Rectangle { public void setWidth(int width) { this.width = width; this.height = height; } public void setHeight(int height) { this.height = height; this.width = width; } } ``` Q : Square 是 Rectangle 的 SubClass , 那 Square 是不是 Rectangle 的 SubType 呢? A : 不一定,要看實作,下面讓我們看一個簡單的實作。 ```java public void testRectangle(Rectangle r) { r.setWidth(10); r.setHeight(20); assertTrue(r.getArea() == 200); } ``` 這個函式如果丟 Square 的物件就會錯,所以 Square 不是 Rectangle 的 SubType。 Q : 那這有什麼重要性呢? A : 因為你不確定當初設計 Rectangle 的人,有沒有保證每個繼承 Rectangle 的類別都是 Rectangle 的 SubType ,你如果知道這點,就可以預期函式的正確性。 Q : 那要怎麼改呢? A : ```java public void testRectangle(Rectangle r) { r.setWidth(width); r.setHeight(height); if(r instanceof Square) { assertTrue(r.getArea() == 400); } else { assertTrue(r.getArea() == 200); } } ``` 但是這程式相當的醜,不好改,而且強迫使用者知道太多細節,這裡的繼承式不合適宜的。 Q : 那什麼時候該繼承呢? A : **!大多數你使用繼承的地方都不該用繼承!**,因為 inheritance 的依賴關係是所有裡面最強的,而太過於依賴不是我們想要的,如何判斷由以下解釋。 * <font color="#92d1ff">**Conclusion**</font> **判斷 A 和 B 是不是 subType 關係, 如果不是 subType 關係你就不該繼承。** Q : 如何有效的判斷 subType 呢 ? 因為帶進去一個一個測有沒有 Error 很智障。 A : 有的。以下 7 個規則為判斷標準,我們以一個例子解釋。 SuperType <- Type <- SubType ```java class SuperType{} class Type extends SuperType{} class SubType extends Type{} ``` ### <font color="#98c37a">**規則一 : Cotravariance of arguement**</font> **1. 當時實作或是繼承 SuperClass 的方法時,你的 input arguement 的數目是要一致的。** **2. SubClass 的方法 input arguements 應該是 SuperClass 相對應的 (SuperType) arguement。** :::success **換句話說,因為 SubType 擁有的東西, SuperType 也擁有,所以如果 arguements 設為 SuperType , SuperType, SubType 都可以 input。** ::: * **Example** <br> ```java class DemoClass { public int compute(Type t); } class DemoClassSub { //1.方法的參數的數目一致 //2.如果你要 Override 參數只能是 SuperType ,不能是 SubType //因為 SubType 有的,SuperType 不一定有。 public int compute(SuperType st); } ``` * **Override arguement** Q : 如果要 override 所有 arguement 參數應該要同一個型態吧!?不然 compile 不會過。 A : 沒錯,但是這只有 java 不會過,因為 java 是個強型別的語言, Liskov 是可以套用在各種語言上的。 ### <font color="#98c37a">**規則二 : Covariance of result**</font> **1. 當實作或是繼承一個 SuperClass 的方法時,你方法 return 的數目是要一樣的。** **2. SubClass 的方法 return Type 應該是 SuperClass 相對應的 return SubClass** :::success **老話一句,因為 SubType 擁有的東西, SuperType 也擁有,所以如果 return 設為 SubType , 只有 SubType 可以使用 return 值。** ::: * **Example** <br> ```java class DemoClass { public Type compute(Type t) { return new Type(); } } class DemoClassSub extends DemoClass { //1.回傳的數目一致 //2.如果你要 Override 回傳只能是 SubType ,不能是 SuperType //因為 SubType 有的,SuperType 不一定有。 @override public Type compute(Type t) { return new SubType(); } } ``` * **Exception rule** 跟 covariance of result 是同一件事,因為 exception 某種程度上也是一種 result。 <br> ```java class DemoClass { public compute() throws Type { //... } public void demo(DemoClass d) { try { //有機會拋出 Type Exception d.compute(); } catch(Type t)//(Exception exp) { //可以抓到 Type 的所有子類 } } } class DemoSubClass { @override public compute() throws SubType { //... } public void demo(DemoClass d) { try { //有機會拋出 SubType Exception d.compute(); } catch(Type t)//(Exception exp) { //可以抓到 Type 的所有子類 } } } ``` ### **小小總結規則一、二** SubType 的好處就是可以放心使用 polymorphism ```java Bird b = getBird(); b.fly(); ``` 我們的目標是 ==**讓 Client 對於 Bird 這個類別的了解越少越好,不用去看所有鳥的 SubType 來決定 Bird 的程式該怎麼寫。**== 只要她回傳給我一定是 Bird 的 SubType 或是 exception 的 SubType,降低了依賴關係。 ### <font color="#98c37a">**規則三 : Pre-condintion rule**</font> >**Pre-condition : An assertion about the state of system before the method is called** >**執行某方法前,必須先將使用到的類別初始化。** >**Pre-condition rule : SubType 的方法所需要的 pre-condition 不能比 Type 的 pre-condition 嚴謹 (strict)。** ### <font color="#98c37a">**規則四 : Post-condintion rule**</font> >**Post-condition : An assertion about the state of system after the method is called** >**執行某方法後,某些指定類別物件不能為 null。** >**Post-condition rule : SubType 的方法所需要的 post-condition 不能比 BaseType 的 post-condition 鬆散 (weak)。** ### **小小總結規則三、四** 這很直覺,因為 SubType 可以代替任何 Type 的地方 所以嚴謹程度 1. pre-condition : SubType > Type 2. post-condition : Type > SubType ### <font color="#98c37a">規則五 : Invariant rule</font> > **Invariant : Some assertion about a specific class property which is always true** > **對於一個類別的永恆不變的法則** * **Example : a kind of Invariant of queue** number of elements in the queue <= capacity > **Invariant rule : 一個 SubType 類別的 invariant 必續包含所有的 Type 的所有 invariant** ### <font color="#98c37a">規則六 : Constraint rule</font> > **Constraint : Some assertion about how class property evloves overtime** > **隨著時間的變化,對於一個類別的改變或是不改變** * **Example : a kind of Constraint of queue** capacity of queue never change after initialized > **Constraint rule : 一個 SubType 類別的 Constraint 必續包含所有的 Type 的所有 Constraint** ### **小小總結規則五、六** **Invariant vs Constraint** * **Example** <br> ```java class MessageErrorDetector { boolean isError = false; public void processMessage(Message m) { if(m.findError()) { isError = true; } } public boolean isErrorDetector() { return isError; } } ``` `MessageErrorDetector` 類別,只要曾經找到 error 那每次 `isErrorDetector` 都會回傳 true。 那 `MessageErrorDetector` 類別的 constraint : 只要偵測到 error,這個物件就會停在 error 的狀態。 假設你今天繼承 `MessageErrorDetector` 寫了 `ResestableMessageErrorDetector` <br> ```java class ResestableMessageErrorDetector extends MessageErrorDetector { public void reset() { isError = false; } } ``` 你覺得這樣很合理,可是這樣就違反 LSP 規則六,因為只要偵測到 error,這個物件就會停在 error 的狀態這個 constraint 就不成立了,所以 `ResestableMessageErrorDetector` 不是 `MessageErrorDetector` 的 SubType。 * <font color="#92d1ff">**Review Definition**</font> ```java class Rectangle { int width; int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } } class Square extends Rectangle { public void setWidth(int width) { this.width = width; this.height = height; } public void setHeight(int height) { this.height = height; this.width = width; } } ``` - [x] 1. Contravariance of argument: 函式 argument - [x] 2. Covariance of result: 函式都回傳 void 沒有拋出 Exception - [x] 3. Pre-condition rule: 都沒有 pre-condition - [x] 4. Post-condition rule: 都沒有 post-condition - [x] 5. Invariant rule: 長方形沒有 Invariant 正方形的 Invariant 就是長寬一樣。 - [ ] 6. Constraint rule: 在 `Rectangle` 的 Contraint 包含了 1. `setWidth` 時不可以動到`height` 2. `setHeight`時不可以動到 `width` 正方形違反了這個 Constraint,所以正方形不是長方形的 SubType * <font color="#92d1ff">**Conclusion**</font> 大多數你繼承時,是不該繼承的,而如果你一定要繼承,用這幾條規則去確認它是不是 SubType ,用這幾條規則去確認。 ## <font color="#F59F00">**ISP : Interface Segregation Principle (介面隔離原則)**</font> * <font color="#92d1ff">**Definition**</font> >**No client should be forced to depend on methods it does not use** >**你不該依賴你根本用不到的東西** * **Example** 一台全功能印表機 Xerox WorkCentre 可以,影印、傳真、掃描... <br> ```java interface IMultiFunction { public void print(); public void scan(); public void fax(); public void copy(); } class XeroxWorkCentre implements IMultFunction { public void print() { //implement print } public void scan() { //implement scan } public void fax() { //implement fax } public void copy() { //implement copy } } ``` * <font color="#92d1ff">**Problems & Resoultions**</font> Q : 如果今天來了一台傳真機 FaxMachine 呢? A : <br> ```java interface IMultiFunction { public void print(); public void scan(); public void fax(); public void copy(); } class FaxMachine implements IMultFunction { public void print(){} public void scan(){} public void fax() { //implement fax } public void copy(){} } ``` 其他用不到的功能只能留白,這很不好。 Q : 哪裡不好? A : 今天想使用 `FaxMachine` 的 Clinet 看到了你實作了 `IMuiltiFunction` 就以為這是個全功能的機器,使用者必續仔細去看 FaxMachine , 才會知道 `print` 是沒用的,這樣對 code 的依賴感很重。 我們先來看這兩個名詞 * <font color="#98c37a">**Cohesion vs Coupling**</font> >**Cohesion : degree to which the various part of a software component are related** >**一個元件裡面,所有資料、方法之間的關聯性** >**Coupling : level of inter dependency between various software component** >**不同元件之間的依賴關係強度** 我們使用 S.O.L.I.D 的目標是 ==**高內聚、低耦合**== 所以回頭看 `IMultiFunction` 的 cohesion 很低,而且還違反了 SRP。 Q : 那要怎麼改? A : ```java interface IPrint { public void print(); } interface IScan { public void scan(); } interface IFax { public void fax(); } interface ICopy { public void copy(); } ``` 如果你要做一個影印+掃描的機器,你就讓該機器實作 IPrint + IScan 兩個不同的介面。 ==**這也就是 Design Pattern(1) Strategy**== * <font color="#92d1ff">**Conclusion**</font> ISP 就是認識極少化以及模組之間的訊息隱藏,我們只要看模組的依賴關係,就可以知道它可以做什麼和不可以做什麼。 ## <font color="#F59F00">**DIP : Dependency Inversion Principle (反向依賴原則)**</font> * <font color="#92d1ff">**Definition**</font> >**High-level modules should not depend on low-level modules. Both should depends on abstractions.** >**Abstractions should not depends on details. Details should depend on abstractions** 這個規則也就是 OCP 的 Conclusion <br><span style="display:block;text-align:center;"><img src="https://i.imgur.com/RGuW71a.png" width="450" height="280"></img></span><br> 依賴關係因為 `CheesePizza` 實作介面 `Pizza` 而反向了,所以稱為 **反向依賴** 應用 DIP 時,我們傾向忽略作業系統和平台機制的穩定背景,我們可以忽略那些具體之間的依賴關係,因為我們信任他們不會改變, ==**我們要避免依賴的是系統中容易變化的具體元素**==,他們正是我在發展的模組。 * **Example** ```java interface ServiceFactory { abstract ConcreteImpl makeService(); } class ServiceFactoryImpl extends ServiceFactory { makeService() { //... } } interface Service { //abstractMethods } class ConcreteImpl extends Service { //concreteImpl } class Application { public Application() { ServiceFactory serviceFactory = new ServiceFactory(); Service service = serviceFactory.makeService(); } } ``` <br><span style="display:block;text-align:center;"><img src="https://i.imgur.com/Ab99HQk.png" width="600" height="400"></img></span><br> 圖中的虛線是一個 ==**架構邊界** 把 abstract 和 concrete 分開== 再具體元件中出現了一個依賴關係,違反了 DIP,也因此違反了 OCP ,但這是很常發生的, **違反原則的這些行為無法完全消除,但可以將其收集到少量的具體元件中,並將系統的其他部分區別開來。**