###### 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/