讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議147:讓接口的職責保持單一 >

建議147:讓接口的職責保持單一

一個類所對應的需求功能越多,引起變化的可能性就越大,單一職責原則(Single Responsibility Principle,簡稱SRP)就是要求我們的接口(或類)盡可能保持單一,它的定義是說「一個類有且僅有一個變化的原因(There should never be more than one reason for a class to change)」,那問題是什麼是職責呢?

職責是一個接口(或類)要承擔的業務含義,或是接口(或類)表現出的意圖,例如一個User類可以包含寫入用戶信息到數據庫、刪除用戶、修改用戶密碼等職責,而一個密碼工具類則可以包含解密職責和加密職責。明白了什麼是類的職責單一,再來看看它有什麼好處。單一職責有以下三個優點:

(1)類的複雜性降低

職責單一,在實現什麼職責時都有清晰明確的定義,那麼接口(或類)的代碼量就會減少,複雜度也就會減少。當然,接口(或類)的數量會增加上去,相互間的關係也會更複雜,這就需要適當把握了。

(2)可讀性和可維護性提高

職責單一,會讓類中的代碼量減少,我們可以一眼看穿該類的實現方式,有助於提供代碼的可讀性,這也間接提升了代碼的可維護性。

(3)降低變更風險

變更是必不可少的,如果接口(或類)的單一職責做得好,一個接口修改只對相應的實現類有影響,對其他的接口無影響,那就會對系統的擴展性、維護性都有非常大的幫助。

既然單一職責有這麼多的優點,那我們該如何應用到設計和編碼中呢?下面以電話通信為例子來說明如何實施單一職責原則:

(1)分析職責

一次電話通信包含四個過程:撥號、通話、回應、掛機,我們來思考一下該如何劃分職責,這四個過程包含了兩個職責:一個是撥號和掛機表示的是通信協議的鏈接和關閉,另外一個是通話和回應所表示的數據交互。

問題是我們依靠什麼來劃分職責呢?依靠變化因素,我們可以這樣考慮該問題:

通信協議的變化會引起數據交換的變化嗎?會的!你能在3G網絡視頻聊天,但你很難在2G網絡上實現。

數據交互的變化會引起通信協議的變化嗎?會的!傳輸2KB的文件和20GB的文件需要的不可能是同一種網絡,用56KB的「小貓」傳輸一個20GB的高清影視那是不可行的。

(2)設計接口

職責分析確定了兩個職責,首先不要考慮實現類是如何設計的,我們首先應該通過兩個接口來實現這兩個職責。接口的定義如下。


//通信協議

interface Connection{

//撥通電話

public void dial();

//通話完畢,掛電話

public void hangup();

}

//數據傳輸

interface Transfer{

//通話

public void chat();

}


(3)合併實現

接口定義了兩個職責,難道實現類就一定要分別實現這兩個接口嗎?這樣做確實完全滿足了單一職責原則的要求:每個接口和類職責分明,結構清晰,但是我相信讀者在設計的時候肯定不會採用這種方式,因為一個電話類要把ConnectionManager和DataTransfer的實現類組合起來才能使用。組合是一種強耦合關係,你和我都有共同的生命期,這樣的強耦合關係還不如使用接口實現的方式呢!而且這還增加了類的複雜性,多出了兩個類。

通常的做法是一個實現類實現多個職責,也就是實現多個接口,代碼如下:


//電話

class Phone implements Connection, Transfer{

//實現各個接口

}


這樣的設計才是完美的,一個類實現了兩個接口,把兩個職責融合在一個類中。你會覺得這個Phone有兩個原因引起了變化,是的,但是別忘記了我們是面向接口編程的,我們對外公佈的是接口而不是實現類。而且,如果真要實現類的單一職責,就必須使用上面的組合模式,這會引起類間的耦合過重、類的數量增加等問題,人為地增加了設計的複雜性。

對於單一職責原則,我建議接口一定要做到職責單一,類的設計盡量做到只有一個原因引起變化。

注意 接口職責一定要單一,實現類職責盡量單一。