設計原則:單一職責(SRP)原則

1 什麼是單一職責(SRP)原則

單一職責原則的英文是 Single Responsibility Principle,縮寫爲 SRP。翻譯過來就是:一個類或者模塊只負責完成一個職責(或者功能)。

所謂職責是指類變化的原因。如果一個類有多於一個的動機被改變,那麼這個類就具有多於一個的職責。而單一職責原則就是指一個類或者模塊應該有且只有一個改變的原因。
很多人認爲這個原則是針對一個類進行描述,在我看來這裏使用模塊這個更加抽象的名詞更爲合適。在設計中,小到一個方法,大到一個模塊我們都應該儘量取遵循單一職責原則。

2 爲什麼要遵循單一職責

相信在我們日常開發中,每個人都遇到過改了一個問題經常會引起另一個問題。其原因大多都是因爲在設計上違背了單一職責原則。
如果一個類承擔的職責過多,就等於把這些職責耦合在一起了。一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。這種耦合會導致脆弱的設計,當發生變化時,設計會遭受到意想不到的破壞。而如果想要避免這種現象的發生,就要儘可能的遵守單一職責原則。此原則的核心就是解耦和增強內聚性,是爲了實現代碼高內聚、低耦合,提高代碼的複用性、可讀性、可維護性。。

如下圖,就是一個非常明顯的不滿足單一職責的類。我們的業務類UserService中包含了add、log、persistence三個方法,同時也包含了三個職責業務添加、日誌記錄、持久化。而實際上我們的業務規則可能是經常變化的,但是我們的持久化方法和日誌記錄是不常變化的。由於耦合在一個類中我們頻繁改動的業務方法,很有可能影響到日誌記錄與持久化。又或者是我們日誌記錄和持久化方法這與業務無關的變動也很有可能影響到我們的正常的也業務邏輯。image.png
正確的做法是將他們分離開來。
image.png

3 如何判斷職責是否足夠單一

對於單一職責這一原則的定義和描述,相信每一個程序員都能張口就來。它理解起來也並不難,即我們不要設計大而全的類或模塊,要儘量設計粒度小、功能單一的模塊。但實際應用過程中確是非常困難的,也是一個非常有爭議性的原則,原因就在於如何去判斷職責是否足夠單一。就像做菜一樣,“放鹽少許”,那麼這個少許到底是多少?你與專業大廚的區別就在於對這個度的把握。
程序設計也是如此,上面那個例子是非常簡單的一眼就能區分職責。但實際開發中往往有很多場景會讓你對職責是否單一的判定難以拿捏。
如下面這個用戶類是否滿足單一職責呢?

image.png

我們可以看到,省市區地址信息在用戶信息類裏面,這種情況下你說它滿足不滿足似乎都有道理。這時就要根據具體的業務場景來看,如果你的業務場景中地址信息僅僅作爲用戶信息的一部分展示來看,這麼設計就是合理的。而如果你的地址信息有一些單獨的邏輯,那麼就是不符合單一職責的。比如電商系統。
此時我們就要把地址信息拆分出來

image.png

再如社交系統中,也許起初地址信息只是用於用戶信息的一部分來展示,但是隨着業務的發展多了一些根據地址信息推薦好友等特殊的需求。那麼我們就要在業務演進的過程中把類進行拆分。
綜上所述,我們可以總結出

  1. 不同的應用場景,對同一個類的職責是否單一的判定,是不一樣的。我們需要具體場景具體分析
  2. 一個類的設計可能已經滿足單一職責原則了,但可能隨着需求的迭代在未來的某個時候就不再滿足單一職責原則了,此時我們沒必要過於未雨綢繆,過度設計。可以先滿足業務需求。隨着業務的發展,如果類變得越來越龐大,代碼越來越多,不再滿足單一職責,這個時候,再把這個類持續進行重構拆分。

4 職責設計是否越單一越好

我們再來思考一個問題,爲了滿足單一職責原則,是不是把類拆得越細就越好呢?
比如我們常用與接口通信的類與序列化操作類

image.png

這時如果我們進一步把Get、 Post, Serialize,Derialize進行拆分到兩個類裏面,看似更好的遵循了單一職責原則,但實際上如果我們的協議格式發生改變或者序列化方式發生改變,那麼就要去更改兩個類的代碼,如果我們漏掉了一個,後果可想而知。

5 不滿足單一職責的壞味道

隨着開發經驗的積累,我們是很容易嗅到一些不滿足單一職責的壞味道的

  1. 類的代碼行數過多
  2. 類依賴的其他類過多
  3. 過長的方法
  4. 私有方法過多
  5. 不容易給類起名字,類名中包含兩個或以上的名詞

小結

單一職責原則是開發中最基礎,最簡單,確是最難把握的一個原則,我們需要不斷地學習以及大量經驗的積累,才能更好的掌握它。此外沒有最好的設計,只有合適的設計,我們需要結合真實的業務場景。記住:無論任何的思想與原則的最終目的是讓我們的代碼具有更好的可讀性、可維護性、可擴展性......

系列文章

設計原則:單一職責(SRP)

設計原則:開閉原則(OCP)

設計原則:裏式替換原則(LSP)

設計原則:接口隔離原則(ISP)

設計原則:依賴倒置原則(DIP)

何謂高質量代碼?

理解RESTful API

關注下方公衆號,回覆“代碼的藝術”,可免費獲取重構、設計模式、代碼整潔之道等提升代碼質量等相關學習資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章