天天看點

單一職責原則(Single Responsibility Principle,SRP)(中)

2.3 使用者管理

使用者、機構、角色管理這些子產品,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的通路控制,通過配置設定和取消角色來完成使用者權限的授予和取消,使動作主體(使用者)與資源的行為(權限)分離),這确實是一個很好的解決辦法。

對于使用者管理、修改使用者的資訊、增加機構(一個人屬于多個機構)、增加角色等,使用者有這麼多的資訊和行為要維護,我們就把這些寫到一個接口中,都是使用者管理類:

  • 使用者資訊維護類圖
  • 單一職責原則(Single Responsibility Principle,SRP)(中)
  • 很有問題,使用者屬性和使用者行為嚴重耦合!這個接口确實設計得一團糟:
  • 應該把使用者資訊抽取成一個BO(Business Object,業務對象)
  • 把行為抽取成一個Biz(Business Logic,業務邏輯)
  • 職責劃分後的類圖
  • 單一職責原則(Single Responsibility Principle,SRP)(中)
  • 重新拆分成兩個接口:

IUserBO

負責使用者的屬性,職責就是收集和回報使用者的屬性資訊

IUserBiz

負責使用者的行為,完成使用者資訊的維護和變更

我們是面向接口程式設計,是以産生了這個UserInfo對象後,當然可以把它當IUserBO接口使用,也可以當IUserBiz接口使用,這看你的使用場景。

要獲得使用者資訊,就當做IUserBO的實作類

要是希望維護使用者的資訊,就把它當做IUserBiz的實作類

IUserInfo userInfo = new UserInfo();
// 我要指派了,我就認為它是一個純粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword("abc");
// 我要執行動作了,我就認為是一個業務邏輯類
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();      

确實這樣拆分後,問題就解決了,分析一下我們的做法,為什麼要把一個接口拆分成兩個?

實際的使用中,更傾向于使用兩個不同的類或接口:一個是IUserBO,一個是IUserBiz

  • 項目中經常采用的SRP類圖
  • 單一職責原則(Single Responsibility Principle,SRP)(中)
  • 以上我們把一個接口拆分成兩個接口的動作,就是依賴了單一職責原則,那什麼是單一職責原則呢?單一職責原則的定義是:應該有且僅有一個原因引起類的變更。

2.4 電話通話

電話通話的時候有4個過程發生:撥号、通話、回應、挂機。

那我們寫一個接口

單一職責原則(Single Responsibility Principle,SRP)(中)

電話過程

單一職責原則(Single Responsibility Principle,SRP)(中)

這個接口有問題嗎?是的,這個接口接近于完美。

單一職責原則要求一個接口或類隻有一個原因引起變化,即一個接口或類隻有一個職責,它就負責一件事情,看看上面的接口:

隻負責一件事情嗎?

是隻有一個原因引起變化嗎?

好像不是!IPhone這個接口可不是隻有一個職責,它包含了兩個職責:

協定管理

dial()和hangup()兩個方法實作的是協定管理,分别負責撥号接通和挂機

資料傳送

chat()實作的是資料的傳送,把我們說的話轉換成模拟信号或數字信号傳遞到對方,然後再把對方傳遞過來的信号還原成我們聽得懂的語言。

協定管理的變化會引起這個接口或實作類的變化嗎?

會的!

那資料傳送(電話不僅可以通話,還可以上網!)的變化會引起這個接口或實作類的變化嗎?

這裡有兩個原因都引起了類變化。這兩個職責會互相影響嗎?

電話撥号,我隻要能接通就成,不管是電信的還是聯通的協定

電話連接配接後還關心傳遞的是什麼資料嗎?

經過分析,我們發現類圖上的IPhone接口包含了兩個職責,而且這兩個職責的變化不互相影響,那就考慮拆分成兩個接口:

職責分明的電話類圖

單一職責原則(Single Responsibility Principle,SRP)(中)

完全滿足了單一職責原則要求,每個接口職責分明,結構清晰,但相信你在設計時肯定不會采用這種方式。一個 Phone類要把ConnectionManager和DataTransfer組合在一塊才能使用。組合是一種強耦合關系,共同生命周期,這樣強耦合不如使用接口實作,而且還增加了類的複雜性,多了倆類。

那就再修改一下類圖:

簡潔清晰、職責分明的電話類圖

單一職責原則(Single Responsibility Principle,SRP)(中)

一個類實作了兩個接口,把兩個職責融合在一個類中。

你可能會說Phone有兩個原因引起變化了呀!

是的,但是别忘了我們是面向接口程式設計,我們對外公布的是接口而非實作類。而且,若真要實作類的單一職責,還就必須使用組合模式了,這會引起類間耦合過重、類的數量增加等問題,人為地增加了設計複雜性。

好處

  • 類的複雜性降低,實作什麼職責都有清晰明确的定義
  • 可讀性提高,複雜性降低,那當然可讀性提高了
  • 可維護性提高,可讀性提高,那當然更容易維護了
  • 變更引起的風險降低,變更是必不可少的。若接口的單一職責做得好,一個接口修改隻對相應的實作類有影響,對其他的接口無影響。這對系統的擴充性、維護性都有非常大幫助。

單一職責原則最難劃分的就是職責。

一個職責一個接口,但問題是“職責”沒有一個量化的标準,一個類到底要負責那些職責?這些職責該怎麼細化?細化後是否都要有一個接口或類?

這些都需要從實際的項目去考慮,從功能上來說,定義一個IPhone接口也沒有錯,實作了電話的功能,而且設計還很簡單,僅僅一個接口一個實作類,實際的項目我想大家都會這麼設計。項目要考慮可變因素和不可變因素,以及相關的收益成本比率,是以設計一個IPhone接口也可能是沒有錯的。

但若純從“學究”理論上分析就有問題了,有兩個可以變化的原因放到了一個接口中,這就為以後的變化帶來了風險。如果以後模拟電話更新到數字電話,我們提供的接口IPhone是不是要修改了?接口修改對其他的Invoker類是不是有很大影響?

單一職責原則提出了一個編寫程式的标準,用“職責”或“變化原因”來衡量接口或類設計得是否優良,但是“職責”和“變化原因”都是不可度量的,因項目而異,因環境而異。

繼續閱讀