1. 程式人生 > >六大設計原則——單一職責原則【Single Responsibility Principle】

六大設計原則——單一職責原則【Single Responsibility Principle】

宣告:本文內容是從網路書籍整理而來,並非原創。

使用者管理的例子

  1. 先看一張使用者管理的類圖:
    這裡寫圖片描述
  2. 再看一眼上面的圖,思考:這樣合理嗎?

  3. 這個介面是一個很糟糕的設計! 使用者的屬性和行為竟然混合在一起!!!

  4. 正確的做法是把使用者的資訊抽取成一個業務物件(Bussiness Object,簡稱 BO),把行為抽取成另外一個介面中,我們把這個類圖重新畫一下:
    這裡寫圖片描述

  5. 這樣劃分成了兩個介面,IUserBO 負責使用者的屬性,IUserBiz 負責使用者的行為,因為是面向的介面程式設計,所有當產生了這個 UserInfo 物件之後,既可以把它當 IUserBO 介面使用,也可以當 IUserBiz 介面使用,類似下面程式碼:

    IUserBiz userInfo = new UserInfo(); 
    
    //我要賦值了,我就認為它是一個純粹的BO 
    IUserBO userBO = (IUserBO)userInfo; 
    userBO.setPassword("abc"); 
    
    //我要執行動作了,我就認為是一個業務邏輯類 
    IUserBiz userBiz = (IUserBiz)userInfo; 
    userBiz.deleteUser();
  6. 問題解決了,但實際中我們更傾向於使用兩個不同的類或介面, 一個就是IUserBO,一個是IUserBiz,如下圖:
    這裡寫圖片描述

  7. 以上我們把一個介面拆分成兩個介面的動作,就是依賴了單一職責原則,那什麼是單一職責原則呢?

    單一職責原則:應該有且僅有一個原因引起類的變更
    SRP 的原話解釋是:There should never be more than one reason for a class to change。

打電話的例子

  1. 電話通話的時候有四過程發生:撥號、通話、迴應、掛機,那麼看下介面圖:
    這裡寫圖片描述
    介面程式碼:

    public interface IPhone { 
    
      //撥通電話 
      public void dial(String phoneNumber); 
    
      //通話 
      public void chat(Object o); 
    
      //迴應,只有自己說話而沒有迴應,那算啥?! 
    public void answer(Object o); //通話完畢,掛電話 public void huangup(); }
  2. 想想,符合單一職責原則嗎?

  3. 其實它有兩個職責:一個是協議管理,一個是資料傳送,diag()和 huangup()兩個方法實現的是協議管理,撥號接通和關閉;chat()和answer()是資料的傳送,把我們說的話轉換成模擬訊號或者是數字訊號傳遞到對方,然後再把對方傳遞過來的訊號還原成我們聽的懂人話。這兩個職責互不影響,所以考慮拆開,如下圖:
    這裡寫圖片描述

  4. 這種設計完全滿足類和介面的單一職責要求,但是一個手機類要把 ConnectionManager 和DataTransfer 組合在一塊才能使用,組合是一種強耦合關係,都是有共同的生命期,這樣的強耦合關係還不如使用介面實現的方式呢,而且還增加了類的複雜性,多了兩個類呀,好,我們修改一下類圖:
    這裡寫圖片描述

  5. 這樣設計才為完美,一個手機實現了兩個介面,把兩個職責融合一個類中,你會覺得這個 Phone有兩個原因引起變化了呀,是的是的,但是別忘記了我們是面向介面程式設計,我們對外公佈的是介面而不是實現類;而且如果真要實現類的單一職責的話,這個就必須使用了上面組合的方式了,那這個會引起類間耦合過重的問題。

    所以,對於介面,我們在設計的時候一定要做到單一,但是對於實現類就需要多方面考慮了,生搬硬套單一職責原則會引起類的劇增,給維護帶來非常多的麻煩;而且過分的細分類的職責也會人為的製造系統的複雜性,本來一個類可以實現的行為非要拆成兩個,然後使用聚合或組合的方式再耦合在一起,這個是人為製造了系統的複雜性,所以原則是死的,人是活的,這句話是非常好的。

單一職責的好處:

  1. 類的複雜性降低,實現什麼職責都有清晰明確的定義;
  2. 可讀性提高,複雜性降低,那當然可讀性提高了;
  3. 可維護性提高,那當然了,可讀性提高,那當然更容易維護了;
  4. 變更引起的風險降低,變更是必不可少的,介面的單一職責做的好的話,一個介面修改只對相應的實現類有影響,與其他的介面無影響,這個是對專案有非常大的幫助。

    單一職責原則最難劃分的就是職責,一個職責一個介面,但是問題是“職責”是一個沒有量化的標準,一個類到底要負責那些職責?這些職責怎麼細化?細化後是否都要有一個介面或類?這個都是需要從實際的專案區考慮的,從功能上來說,定義一個 IPhone 介面也沒有錯,實現了電話的功能呀,而且設計還很簡單,就一個介面一個實現類,真正的專案我想大家一般都是會這麼設計的,從設計原則上來看就有問題了,有兩個可以變化的原因放到了一個介面中了,這就為以後的變化帶來了風險,我從 2G 通訊協議修改到 3G 通訊,你看看你提供出的介面 IPhone 是不是要修改了?介面修改對其他的 Invoker 是不是有很大影響?!

方法的單一職責原則

單一職責使用於介面、類,同時也使用方法,什麼意思呢?一個方法儘可能做一件事情,比如一個方法修改使用者密碼,別把這個方法放到“修改使用者資訊”方法中,這個方法的顆粒度很粗,比如這樣一個方法:
這裡寫圖片描述

在 IUserManager 中定義了一個方法叫 changeUser,根據傳遞的 type 不同,把可變長度引數changeOptions 修改到 userBo 這個物件上,並呼叫持久層的方法儲存到資料庫中。在我的專案組中如果有人寫了這樣一個方法,我不管他寫了多上程式化了多少工夫,一律重寫!原因是:方法職責不清晰,不單一,一般方法設計成這樣的:
這裡寫圖片描述
你要修改使用者名稱稱,就呼叫 changeUserName 方法,你要修改家庭地址就呼叫 changeHomeAddress,你要修改單位單戶就呼叫 changeOfficeTel 方法,每個方法的職責就非常清晰,這也是一個良好的設計習慣。
所以,不管是對介面、類、方法使用了單一規則原則,那麼快樂的就不僅僅是你了,還有你專案的成員,你的板,減少了因為變更引起的工作量呀,加官進爵等著你么!

疑惑

你看到這裡,就會問我,你寫是類的設計原則嗎?你通篇都在說介面的單一職責,類的單一職責你都違背了呀,呵呵,這個還真是的,我的本意是想把這個原則講清楚,類的單一職責嘛,這個很簡單,但當我回頭寫的時候,發覺才不是這麼回事,翻看了以前一些設計和程式碼,基本上拿的出手的類設計都是和單一職責向違背的,靜下心來回憶,發覺每一個類這樣設計都是有原因的。這幾天我查閱了 wikipedia、oodesign 等幾個網站,專家和我也有類似的經驗,基本上類的單一職責都用了類似的一句話來說“This is sometimes hard to see” ,這句話翻譯過來就是“這個有時候很難說” ,是的,類的單一職責確實受非常多的因素制約,純理論的來講,這個原則是非常優秀的,但是現實有現實難處,你必須去考慮專案工期、成本、人員技術水平、硬體情況、網路情況甚至有時候還要考慮政府政策、壟斷協議等等原因。

所以,對於單一職責原則,我的建議是介面一定要做到單一職責,類設計儘量只有一個原因引起變化。