技術交流

物件導向軟體設計原則 - 單一職責原則

費鵬 費鵬

就一個類而言,應該僅有一個引起它變化的原因。

所謂職責,我們可以理解它為功能,就是設計的這個類功能應該只有一個,而不是兩個或更多。也可以理解為引用變化的原因,當你發現有兩個變化會要求我們修改這個類,那麼你就要考慮拆分這個類了。因為職責是變化的一個軸線,當需求變化時,該變化會反映類的職責的變化。就像一個人身兼數職,而這些事情相互關聯不大,甚至有衝突,那他就無法很好的解決這些職責,應該分到不同的人身上去做才對。同樣如果一個類承擔的職責過多,就等於把這些職責耦合在了一起。一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。這種耦合會導致脆弱的設計,當變化發生時,設計會遭受到意想不到的破壞。

考慮程式1.1中的Modem介面。大多數人會認為這個介面看起來非常合理。該介面所聲明的4個函數確實是數據機所具有的功能。

程式1.1

interface Modem
{
  public void dial(string pno);
  public void hangup();
  public void send(char c);
  public void recv();
} 

然而,該介面中卻顯示出兩個職責。第一個職責是連接管理;第二個職責是資料通信。Dial和hangup函數進行數據機的連接處理,而send和recv函數進行資料通信。

這兩個職責應該被分離嗎?這依賴于應用程式變化的方式。如果應用程式的變化會影響連接函數的簽名(signature),那麼這個設計就具有僵化性的臭味,因為調用send和recv的類必須要重新編譯,部署的次數常常會超過我們希望的次數。在這種情況下,這兩種職責應該被分離,如程式1.2所示。這樣做避免了客戶應用程式和這兩職責耦合在一起。

程式1.2

interface DataChannel
{
  public void send(char c);
  public void recv();
}
interface Connection
{
  public void dial(string pno);
  public void hangup();
}

在進行軟體設計時,如果使用SRP原則,可以消除耦合,減小因需求變化引起代碼僵化性臭味。SRP是所有原則中最簡單的之一,也是最難正確運用的之一。所以在使用SRP時要注意如下幾點:

  • 一個合理的類,應該僅有一個引起它變化的原因,即單一職責
  • 在沒有變化徵兆的情況下應用SRP或其他原則是不明智的。如果應用程式的變化方式總是導致這兩個職責同時變化,那麼就不必分離它們,分離它們反而會使增加了問題的複雜性
  • 在需求實際發生變化時,就應該應用SRP等原則對代碼進行重構,並且儘量做到能適應這類需求的變化
  • 在軟體發展過程中,我們如果進行單元測試,此時單元測試代碼就是程式的使用者,這同樣也會迫使我們在設計出現問題之前分離不合理代碼,所以建議大家在進行開發時,最好能進行單元測試

參考資料:
敏捷軟體發展-原則、模式與實踐》 Robert C. Martin著 鄧輝譯 孟岩審 清華大學出版社
《.Net與設計模式》 甄鐳 著 電子工業出版社
《Java與模式》 閻巨集