---
# System prepended metadata

title: SOLID五原則
tags: [程式]

---

###### tags: `程式`
[TOC]
# SOLID五原則

## Single Responsibility Principle
:::info
定義：A module should have one, and only one, reason to change
- 一個模組只會有==一個理由會使其改變==。
:::
- 比較不太正確的理解是創造class或function時，不要傳太多的參數和太多的方法在裡面執行。
### 例子
#### 錯誤的例子Employee class
```java=
class Employee {
  public int calculateMonthlySalary() {
    //...
  }
  public HoursReport produceMonthlyHoursReport() {
    //...
  } 
  public void saveEmployee() {
    //...
  }
}
```
Employee裡面有三個功能 
1. ==計算每月薪水==
2. ==產生每月工時報告== 
3. ==儲存Employee資料==

#### 這個類別有部分需要”reasons to change”呢??
1. ==會計部==想改變時薪 calculateMonthlySalary() 要改
2. ==人資==想改變加班計算方式 produceMonthlyHoursReport() 要改
3. ==工程師==想改變Employee的encode方式 saveEmployee() 要改

#### 慘劇
你們公司的資深工程師剛走 你是個剛上工不久的新手碼農 今天人資跟你說 Hey 政府頒布政令 原本工作超過8小時算加班 現在要工作超過10小時才算加班 請幫我改變一下HoursReport的產生方式
![](https://i.imgur.com/l4QupDV.png)
:::warning
原本應該要改變==產生每月工時報告==，變成寫在==計算每月薪水==，導致每個月公司多付錢給員工。
:::
#### 改法
```java=
class Employee {
  private String id;
  public String getId(){
   return id;
  }
}
class PaymentService{
  public int calculateMonthlySalary(Employee employee) {
  //...
  }
}
class WorkHoursServiceService{
  public HoursReport produceMonthlyHoursReport(Employee employee) {
  //...
  }
}
class EmployeeDAO{
  public void saveEmployee(Employee employee) {
  //...
  }
}
```
### 結論
> **寫完class想想，他會應為甚麼理由而被改寫**。

## Open–closed principle
:::info
定義：Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
- 軟體可以==開放==擴充，但是==封閉==修改
- 重點在於==加上抽象==，讓你個程式碼增加新功能時，不用改寫舊的程式碼
:::
- open：要能夠提供彈性的實作
- close：==要有一個不會改變的抽象==
### 例子
- 請看Design Pattern中，State Pattern的例子
https://hackmd.io/@Foxword/SJvMJGhp_
## Liskov Substitution Principle
:::info
定義：If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.)
- 子物件可以取代父物件，而不會影響父物件的功能。
:::
### 例子
- 用兩種物件說明：==長方形==、==正方形==
#### 長方形
```java=
class Rectangle{
  int width;
  int height;
  public void setWidth(int w){
    width = w;
  }
  public void setHeight(int h){
    height = h;
  } 
}
```
#### 正方形 繼承 長方形
```java=
class Square extends Rectangle {
  public void setWidth(int w){
    width = w;
    height = w;
  }
  public void setHeight(int h){
    height = h;
    width = h;
  } 
}
```
#### 出錯點
```java=
public void testRectangle(Rectangle r) {
  r.setWidth(10);
  r.setHeight(20);
  assertTrue(r.getArea() == 200);
}
```
- 當長方形有一個檢查面積是否正確的函數時，
    - 長方形物件在這裡面檢查==正確==
    - 正方形物件在這裏面檢查==錯誤==
:::info
因為當檢查面積的function裡，你會宣告兩次長方形不同的長和寬，但是在正方形中兩者是相同的，在第二次設高時，會把第一次所設的寬給覆蓋住，導致這檢查函式，覺得你計算出的面積有誤。
:::
### 結論
- 用法：用來決定你是否要使用繼承。
- 當你宣告新的物件要採取繼承時，先想想，繼承後這個物件會不會造成既有父物件的某些funtion出現錯誤。

## Interface Segregation Principle 
:::info
定義：No client should be forced to depend on methods it does not use.
- 你==不應該去依賴==，你根本不會用到的東西
:::
### 例子 
#### 印表機
```java=
interface IMultiFunction
  public void print();

  public void scan();

  public void fax();

  public void copy();
}
```
- 你有一個印表機的interface，你之後會把它實作到各個不同的印表機上
#### 具有==所有功能==的印表機
```java=
class XeroxWorkCentre implements IMultiFunction
  @Override
  public void print(){
    // implement print
  }

  @Override
  public void scan(){
    // implement scan
  }

  @Override
  public void fax(){
    // implement fax
  }

  @Override
  public void copy(){
    // implement copy
  }
}
```
#### 只有==傳真功能==的印表機
```java=
class FaxMachine implements IMultiFunction
  @Override
  public void print(){
    
  }

  @Override
  public void scan(){
    
  }
  
  @Override
  public void fax(){
    // implement fax
  }

  @Override
  public void copy(){
    
  }
}
```
:::warning
這造成了，只有傳真功能的印表機裡面有一堆不需要的功能。
:::
### 解法
```java=
interface IPrint{
  public void print();
}

interface IScan{
  public void scan();
}

interface IFax{
  public void fax();
}

interface ICopy{
  public void copy();
}
```
:::info
把個別功能用單一interface去寫就好，不需要把所有功能用單一interface去裝。
:::
### 結論
- 從模組的==依賴關係== 就知道模組可以做什麼
- 從模組的==依賴關係== 就知道模組不可以做什麼
- 迫使你必須要把你的大介面 分離成眾多小介面



## Dependency Inversion Principle
:::info
1. 高層次的模組不應該依賴於低層次的模組，兩者都應該依賴於抽象介面。
2. 抽象介面不應該依賴於具體實現。而具體實現則應該依賴於抽象介面。
:::
### 圖例
![](https://i.imgur.com/nlNeFkG.png)
- 就像OCP原則一樣，下方每個裝置都是新的功能，而我們當要增加新的功能的時候，並不會改變原本的程式碼，而是在創在新的功能，並用抽象去管理他們。
### 控制反轉
- 讓底層的模組可以依賴抽象
- 用dependency injection去實現它
### 依賴注入
- Class A中用到了Class B的物件b，一般情況下，需要在A的代碼中顯式的new一個B的物件。

- 採用依賴注入技術之後，A的代碼只需要定義一個私有的B物件，不需要直接new來獲得這個物件，而是通過相關的容器控制程式來將B物件在外部new出來並注入到A類里的參照中。
## 參考連結
### 全部五個原則
- https://ithelp.ithome.com.tw/articles/10230018

### SRP
- https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E7%9B%AE%E9%8C%84-b33fdfc983ca
### jyt0532's Blog
- SRP：https://www.jyt0532.com/2020/03/18/srp/
- OCP：https://www.jyt0532.com/2020/03/19/ocp/
- LSP：https://www.jyt0532.com/2020/03/22/lsp/
- ISP：https://www.jyt0532.com/2020/03/23/isp/
- DIP：https://www.jyt0532.com/2020/03/24/dip/