1. 程式人生 > >面向物件六大原則——單一職責原則

面向物件六大原則——單一職責原則

什麼是單一職責原則(Single Responsibility Principle, SRP)

 在講解什麼是單一職責原則之前,我們先說一個例子,吊一下口味:我們在做專案的時候,會接觸到使用者,機構,角色管理這些模組,基本上使用的都是RBAC模型(Role-Based Access Control,基於角色的訪問控制, 通過分配和取消角色來完成使用者許可權的授予和取消,使動作主體(使用者)與資源的行為(許可權)分離)。現在假設這樣一種場景,我們把使用者管理,修改使用者資訊,增加機構,增加角色等維護資訊寫到一個介面中進行管理,類圖如下:

分析上面的類圖我們會發現,這樣的設計是非常不合理的,使用者的屬性和使用者的行為是兩種不同的業務模式,把它們都寫在一個類中顯然不行。我們應該把使用者的資訊抽取成一個BO(Business Object, 業務物件), 把行為抽取成一個Biz(Business Logic, 業務邏輯), 重新設計的類圖如下:

SRP在類或介面中的使用

 SRP的原話是:There should never be more than one reason for a class to change.翻譯過來其實也很好懂:應該有且僅有一個原因引起類的變更。看下面的例子:

/**
上面的的類圖對應的介面入下
*/
public interface IPhone{
  //撥通電話
  public void dial(String phoneNumber);

  //通話
  public void chat(Object o);

  //結束通話電話
  public void hangup();
}

在看到這個介面的時候,我們都會認為這樣的設計是沒有問題的,撥通電話,通話,結束通話電話寫在同一個接口裡面並沒有什麼錯。但是,我們仔細分析,這個介面真的沒有問題嗎?單一職責原則要求一個介面或類只有一個原因引起變化,也就是說一個介面或一個類只有一個原則,它就只負責一件事。

但我們分析上面這個介面,卻發現它包含了兩個職責:一個時協議管理,一個是資料傳送。dial()和hangup()兩個方法實現的是協議管理,分別是撥通電話和掛機。chat()實現的是資料傳送,把我們說的話轉換成模擬訊號或數字訊號傳遞給對方,然後再把對方傳遞過來的訊號還原成我們聽得懂的語言。這裡的協議接通和資料傳送的變化都會引起該介面或實現類的變化。我們想一想,這兩個職責會相互影響嗎?不管是什麼協議,協議接通只負責將電話接通就行,而資料傳輸只需要傳輸資料,不必要去管協議是如何接通的。所以通過分析,IPhone介面包含了兩個職責,而且這兩個職責的變化不互相影響,這就可以考慮分成兩個介面,類圖如下:

觀察上面的類圖,我們發現這樣的設計會比原來籠統的設計優雅的多,現在的設計在職責上比原來更加分明,讓人一眼就能看出這個介面負責的是什麼。也許有人會問,Phone這個類實現了兩個介面,又把兩個職責融合在了一個類中,那麼是不是就有兩個原因引起了它的變化了呢?別忘了,我們是面向介面程式設計,我們對外公佈的是介面(API),並非實現類,給你提供了模板,在介面層面已經為你明確了職責,那麼具體的實現怎麼弄就需要開發者去考慮了。

SRP也適用於方法

 其實,單一職責原則不僅適用於類,介面,同樣適用於方法中。這要舉一個例子了,比如我們做專案的時候會遇到修改使用者資訊這樣的功能模組,我們一般的想法是將使用者的所有資料都接收過來,比如使用者名稱,資訊,密碼,家庭地址等等,然後統一封裝到一個User物件中提交到資料庫,我們一般都是這麼幹的,就如下面這樣:

 其實這樣的方法是不可取的,因為職責不明確,方法不明確,你到底是要修改密碼,還是修改使用者名稱,還是修改地址,還是都要修改?這樣職責不明確的話在與其他專案成員溝通的時候會產生很多麻煩,正確的設計如下:

SRP的優點

  • 類的複雜性降低,對於實現什麼職責都有清晰明確的定義。
  • 可讀性提高。
  • 可維護性提高。
  • 變更引起的風險降低,一個介面的修改只對相應的實現類有影響,對其他介面無影響,這對系統的擴充套件性,維護性都有非常大的幫助。

參考書籍

  • 《設計模式之禪》