什么是單一職責(zé)原則(SRP)?
單一職責(zé)原則(SRP)的職責(zé)被定義為“引起變化的原因”。如果我們有兩個動機(jī)去改寫一個方法,那么這個方法就具有兩個職責(zé)。每個職責(zé)都是變化的一個軸線,如果一個方法承擔(dān)了過多的職責(zé),那么在需求的變遷過程中,需要改寫這個方法的可能性就越大。
此時,這個方法通常是一個不穩(wěn)定的方法,修改代碼總是一件危險的事情,特別是當(dāng)兩個職責(zé)耦合在一起的時候,一個職責(zé)發(fā)生變化可能會影響到其他職責(zé)的實(shí)現(xiàn),造成意想不到的破壞,這種耦合性得到的是低內(nèi)聚和脆弱的設(shè)計。
因此,SRP原則體現(xiàn)為:一個對象(方法)只做一件事情。
設(shè)計模式中的SRP原則
SRP原則在很多設(shè)計模式中都有著廣泛的運(yùn)用,例如代理模式、迭代器模式、單例模式和裝飾者模式。
- 代理模式:代理對象用來處理本體的數(shù)據(jù)以及對本體操作的邏輯,而本體僅僅是負(fù)責(zé)最原始的職責(zé)
- 迭代器模式:迭代是一個職責(zé),如何對迭代的數(shù)據(jù)進(jìn)行處理是另外一個職責(zé)
- 單例模式:一個職責(zé)是管理單例,一個職責(zé)是實(shí)現(xiàn)邏輯
- 裝飾者模式:使用裝飾者模式的時候,我們通常讓類或者對象一開始只具有一些基礎(chǔ)的職責(zé),更多的職責(zé)在代碼運(yùn)行時被動態(tài)裝飾到對象上面。裝飾者模式可以為對象動態(tài)增加職責(zé),從另一個角度來看,這也是分離職責(zé)的一種方式。
何時應(yīng)該分離職責(zé)?
SRP原則是所有原則中最簡單也是最難正確運(yùn)用的原則之一。
要明確的是,并不是所有的職責(zé)都應(yīng)該一一分離。
一方面,如果隨著需求的變化,有兩個職責(zé)總是同時變化,那就不必分離他們。比如在ajax請求的時候,創(chuàng)建xhr對象和發(fā)送xhr請求幾乎總是在一起的,那么創(chuàng)建xhr對象的職責(zé)和發(fā)送xhr請求的職責(zé)就沒有必要分開。
另一方面,職責(zé)的變化軸線僅當(dāng)它們確定會發(fā)生變化時才具有意義,即使兩個職責(zé)已經(jīng)被耦合在一起,但它們還沒有發(fā)生改變的征兆,那么也許沒有必要主動分離它們,在代碼需要重構(gòu)的時候再進(jìn)行分離也不遲。
違反SRP原則
在人的常規(guī)思維中,總是習(xí)慣性地把一組相關(guān)的行為放到一起,如何正確地分離職責(zé)不是一件容易的事情。
我們也許從來沒有考慮過如何分離職責(zé),但這并不妨礙我們編寫代碼完成需求。對于SRP原則,許多專家委婉地表示“This is sometimes hard tosee.”。
一方面,我們受設(shè)計原則的指導(dǎo),另一方面,我們未必要在任何時候都一成不變地遵守原則。在實(shí)際開發(fā)中,因?yàn)榉N種原因違反SRP的情況并不少見。比如jQuery的attr等方法,就是明顯違反SRP原則的做法。jQuery的attr是個非常龐大的方法,既負(fù)責(zé)賦值,又負(fù)責(zé)取值,這對于jQuery的維護(hù)者來說,會帶來一些困難,但對于jQuery的用戶來說,卻簡化了用戶的使用。
在方便性與穩(wěn)定性之間要有一些取舍。具體是選擇方便性還是穩(wěn)定性,并沒有標(biāo)準(zhǔn)答案,而是要取決于具體的應(yīng)用環(huán)境。比如如果一個電視機(jī)內(nèi)置了DVD機(jī),當(dāng)電視機(jī)壞了的時候,DVD機(jī)也沒法正常使用,那么一個DVD發(fā)燒友通常不會選擇這樣的電視機(jī)。但如果我們的客廳本來就小得夸張,或者更在意DVD在使用上的方便,那讓電視機(jī)和DVD機(jī)耦合在一起就是更好的選擇。
SRP原則的優(yōu)缺點(diǎn)
SRP原則的優(yōu)點(diǎn)是降低了單個類或者對象的復(fù)雜度,按照職責(zé)把對象分解成更小的粒度,這有助于代碼的復(fù)用,也有利于進(jìn)行單元測試。當(dāng)一個職責(zé)需要變更的時候,不會影響到其他的職責(zé)。
但SRP原則也有一些缺點(diǎn),最明顯的是會增加編寫代碼的復(fù)雜度。當(dāng)我們按照職責(zé)把對象分解成更小的粒度之后,實(shí)際上也增大了這些對象之間相互聯(lián)系的難度。