1. 程式人生 > >設計模式(06)——設計原則(1)

設計模式(06)——設計原則(1)

# 設計原則 設計原則,是設計模式的內功心法,基本所有的設計模式都是基於設計原則進行的具體化,如果說設計模式是如何操作的話,那麼設計原則就是為何這麼做的基石,因此,只要我們能充分理解設計原則,那麼在此基礎上,對設計模式就能更好的理解,甚至能自己設計出一種設計模式來。
## 單一職責原則 ### 定義 一個**類或模組**,只需要完成一個範圍的功能,而不要搞得大而全。
### 場景 例如我們設計一個社交網站,現在要儲存使用者資訊,類設計如下: ```java public class UserInfo { private String name; private String like; private String location; } ``` 現在,我們想想該類的設計是否符合單一職責原則?

答案是可能符合,也可能不符合。那麼判斷依據是什麼呢?

原因就是類或模組職責的判斷是根據業務來定的,並沒有一個普遍認同的規則。例如,如果該需要要的網站還提供賣東西的功能,那麼使用者的地址資訊,就是一個十分關鍵的資訊,且該塊功能就需要抽離出來作為一個單獨的模組,此時,地址資訊放在這裡就不合適了,違反了單一職責原則。

但如果地址資訊,只是一個值物件,也就是說其只是一個展示屬性,那麼放在這裡就是合適的。

**綜上所述,可以看到單一職責原則並不是設計出來就一成不變的,其需要結合業務發展的具體情況來判斷。因此我們在設計之初,可以考慮一個大而全的類,但隨著業務的發展需要,需要持續不斷的進行優化(也就是持續重構的思想)。**
### 用處 單一職責,因為類的設計比較小而精,因此可以極大提高程式碼的可維護性和可讀性。

此外因為每個類或模組只涉及自己的功能部分,因此,也做到了高內聚。
### 其他 但類的設計也不是越單一越好,因為如果拆分的過細的話,可能上層一個類需要修改,會導致下層所有依賴其的類都要修改,又影響了程式碼的可維護性,因此還是要根據業務需要來合理評估,重點是感覺要對。
## 開閉原則 ### 定義 字面意思,一個類的設計,應該要對拓展開放,對修改關閉。因此這裡的重點就是以下定義該如何判斷: - 什麼樣的程式碼修改是拓展; - 什麼樣的程式碼修改是修改; - 修改程式碼就一定是違反了該原則嗎 ### 場景 ```java public static void main(String[] args) { Demo demo = new Demo(); demo.consume(1); } // 根據傳遞過來的級別來進行不同的會員邏輯判斷 public void consume(int type) { if (type == 1) { Console.log("您好,1級會員!"); } if (type == 2) { Console.log("您好,2級會員!"); } } ``` 現在,又提出一個新的需求,還需要根據對應的會員等級進行對應的金額扣除,如果是上述的設計方式,那麼修改的方式則是下面這樣: ```java public void consume(int type, int price) { if (type == 1) { Console.log("您好,1級會員,扣除金額{}", price); } if (type == 2) { Console.log("您好,2級會員,扣除金額{}", price); } } ``` 很明顯,這樣的方式有問題,如果還要再傳遞一個欄位,例如優惠比例,那麼依照該方案,則還需要修改介面定義,這就意味著呼叫方都需要修改,測試用例也需要對應的修改。 那麼如果按照開閉原則的話,該如何設計呢?

首先我們對程式碼進行下重構 ```java // 將所有相關屬性封裝起來 public class Vip { private int type; private int price; private int radio; } ``` 針對每種處理方式,根據他們的公有行為抽象出一個抽象層: ```java public interface VipHandler { void consume(Vip vip); } ```
每種特殊處理方式實現對應的抽象: ```java public class FirstVipHandler implements VipHandler { @Override public void consume(Vip vip) { if (vip.getType() == 1) { Console.log("您好,1級會員,扣除金額{}", vip.getPrice() * vip.getRadio()); } } } public class SecondVipHandler implements VipHandler { @Override public void consume(Vip vip) { if (vip.getType() == 2) { Console.log("您好,2級會員,扣除金額{}", vip.getPrice() * vip.getRadio()); } } } ```
通過這樣的處理方式,在每次接到新的任務後,就不需要重新修改原有的邏輯方法,可以直接進行拓展即可: ```java // 根據傳遞過來的級別來進行不同的會員邏輯判斷 public void consume(Vip vip, VipHandler vipHandler) { vipHandler.consume(vip); } ``` ### 其他 可以看到即使是上述的方式來拓展程式碼,仍舊會修改原有程式碼,那麼這種方式是違反了開閉原則嗎?

在這裡,我們判斷其並符合了開閉原則,因為我們判斷是修改還是拓展,並不能只是簡單的根據看是否修改了原有程式碼,真正核心的關鍵問題應該是:
- **改動沒有破壞原有程式碼的正常執行;** - **改動沒有破壞原有單元測試** **
在上述的修改後,我們如果加一種特殊的情況,並沒有修改到原先的處理邏輯類,這也就意味著原先的程式碼不會引入一些可能的 bug,針對原始程式碼的測試用例也還是可以照常的進行編寫,而不用再根據新的改動而進行改動。
### 用途 開閉原則的關鍵點是程式碼的**可拓展性**,即如何快速的擁抱變化,當每次新的任務來後,不必修改原始程式碼,而直接在原有的基礎上進行拓展即可。

**關閉修改是保持原有程式碼的穩定性**。
## 里氏替換原則 ### 定義 **子類物件可以代替父類物件出現的任何地方,並保證原來程式邏輯行為不被破壞。**
**
因為要保證子類物件不能破壞原有程式邏輯行為,因此該方式跟多型的區別是:**如果子類進行了重寫,並在重寫的邏輯中加入了跟父類對應方法不同的邏輯,那麼該方式可以稱之為多型,但就不符合里氏替換原則了。 ### ### 用途 該原則最重要的作用是指導子類的設計,保證在替換父類的時候,不改變原有程式的邏輯行為。

在這裡,重點是邏輯行為的不改變,這就意味著,我們可以對實現的細節進行修改,只要保證業務含義不變,那麼就是符合里氏替換原則的。

因此,針對這種情況,有一種用途是可以 **改進** 原有的實現,例如原先採用的排序演算法比較低效,那麼可以設計一個子類,然後重寫對應排序演算法,保證邏輯不發生變化。重點是,我們做的是改進,不管如何改,排序的業務含義是不變的。

實現方式,則是**按照協議進行程式設計,關鍵是子類重寫過程中,要保證不破壞原有函式宣告的輸入、輸出、異常以及註釋中羅列的任何特殊情況宣告。**

## 介面隔離原則 ### 定義 首先,我們要對介面進行定義,明確其特殊含義,對於介面來說,我們將其分為兩種型別的表現形式。 - 一種語法定義,其代表了一組方法的集合; - 向外暴露的單個API介面或函式實現
下面,我們分別對其進行介紹。
### 場景:一組API聚合 ```java public interface UserInfoService { boolean login(); void getUserInfoByPhone(); void deleteUserByPhone(); } ``` 看上述這個介面定義是否符合介面隔離原則???

其實這跟單一職責原則一樣,也是要看業務發展的。例如,如果該介面是提供給後臺管理系統來使用的,那麼沒有問題,作為一個後臺系統的 admin 許可權人員當然可以有很多操作的能力。

但如果該介面是給第三方使用者來使用的話,就不是很合適了。因為刪除操作是一個高許可權能力,作為使用者來說,一般是沒有許可權做的,那麼在設計時,對應實現類就不應該實現全部介面內定義的方法,這就是介面隔離原則中所說的,**不強制依賴介面中的所有方法。**
**
而是,根據具體的定義,將其進行拆分,對應許可權的實現類實現對應的許可權行為。
### 場景:單個API介面或函式實現 ```java public class Statistics { private int max; private int min; private int sum; //...... public Statistics co