---
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 ,但這是很常發生的,
**違反原則的這些行為無法完全消除,但可以將其收集到少量的具體元件中,並將系統的其他部分區別開來。**