單一職責原則(Single Responsibility Principle, SRP)是Bob大叔提倡的S.O.L.I.D五大設計原則中的第一個。其中,職責(Responsibility)被表述爲「變化的緣由」(reason to change);SRP被表述爲「一個類應該有且只有一個變化的緣由」。但若是光從字面去理解,SRP很容易讓人望文生義產生誤解。本文但願能闡明SRP的本質,達到避免誤解和指導設計的目的。
對於設計原則的理解應該首先從它的動機入手,即遵循這個原則帶來的好處是什麼?對於SRP來說,若是遵循「一個類應該有且只有一個變化的緣由」是因,那麼「任何一個變化只會影響一個類」就是果。可見SRP的動機主要是系統的可維護性,即下降適應變化的成本。 java
對於單功能的類來說,比較容易遵循SRP,好比:把string轉換爲DateTime類型的工具類。理解SRP的難點在於在多功能類的情形,即不容易理解多功能與單變化的矛盾。讓咱們先來看一個Modem類的例子,Modem具備4個功能:撥號,掛斷,發送數據,接收數據: 工具
class Modem { public void Dial(string aPno) {…} public void Hangup() {…} public void Send(char aData) {…} public char Receive() {…} }
咱們先來考察一下Modem類的變化點,並將其分爲兩類:1.Modem類的內部細節變化,好比Dial、Hangup、Send、Receive等具體實現發生了變化;2.Modem類總體性的變化,好比Modem類須要增長Poweron和Poweroff方法。上面的Modem類具備多個變化點,不符合SRP。 設計
咱們先來考察一下Modem類的變化點,並將其分爲兩類:1.Modem類的內部細節變化,好比Dial、Hangup、Send、Receive等具體實現發生了變化;2.Modem類總體性的變化,好比Modem類須要增長Poweron和Poweroff方法。上面的Modem類具備多個變化點,不符合SRP。 code
interface IDialable{ void Dial(string aPno); } class Dialer : IDialable{ public void Dial(string aPno){…} } class Modem{ public void Dial(string aPno) { m_dialer.Dial(aPno); } private IDialable m_dialer; … //hangup, send, receive相似 }
上面的設計將各個功能點抽象爲單一的接口,將變化封裝在穩定的接口背後,再經過組合的方式創建起總體的Modem類與局部的功能點的聯繫。這樣就避免了低層的變化傳導到高層。 接口
可見,理解SRP的關鍵在於理解類的抽象層次,高層次的類是高層概念的抽象,低層次的類是低層概念的抽象。低層的變化隻影響低層類,高層的變化隻影響高層類。對於遵照SRP的設計,必定具備很好的抽象層次,所以不妨以SRP爲指導和檢驗,幫助咱們設計出好的類來。最後,我用幾個關鍵詞梳理SRP的脈絡:ip
類只有一個變化的緣由 >> 一個變化隻影響一個類 >> 變化隻影響其相應層次的類ci