「分離職責」是常用的一個重構策略,當一個類擔任的職責太多時,應按職責將它拆分紅多個類,每一個類分別承擔「單一」的職責,也就是讓每一個類專心地作「一件事情」。html
在面向對象編程中,SRP原則是一個很是重要的原則(SOLID原則都很重要),在展現示例前,咱們先了解一下SRP原則是什麼,以及它有什麼做用。編程
SRP原則的定義是這樣的:ide
There should never be more than one reason for a class to change.this
就一個類而言,應該僅有一個引發它變化的緣由。spa
When a class has more than one responsibility, there are also more triggers and reasons to change that class. A responsibility is the same as 「a reason for change」 in this context.設計
由於每個職責都是變化的因子,當需求變化時,該變化一般反映爲類的職責的變化。
若是一個類承擔了多於一個的職責,那麼就意味着引發它的變化的緣由會有多個,等同於把這些職責耦合在了一塊兒。
一個職責的變化可能會抑制到該類完成其餘職責的能力,這樣的耦合會致使脆弱的設計。htm
在設計類或接口時,若是可以遵照SRP原則,則會帶來如下優勢:對象
Separating responsibility can be done by defining for every responsibility a class or an interface.blog
應該爲每一項職責定義類或接口的方式實現職責分離。接口
SRP原則堪稱是SOLID原則裏面最簡單的一個原則,但也能夠說是最難的一個。
它的「簡單」之處在於它很容易被理解,「困難」之處在於不少人在軟件設計過程當中,很難真正地抓住關鍵點。
對於開發者來講,「分離職責」存在四個難點,也是開發者在使用這種重構策略時須要慎重考慮的地方
「職責」單一是相對的,每一個人看事情的角度,對業務的理解程度是不盡相同的,這致使了人們對職責的定義和細化程度的差別性。一樣一個業務,有些人從角度A出發,在對業務提煉概括總結後,得出三項職責:J、K、L。而有些人則從角度B出發,概括總結出兩項職責:X、Y。
在設計接口時,這兩人天然而然地會設計出不一樣的接口,兩人設計的接口個數和表達的語義也各不相同。
拿裝修房子來講,業主一般會委託裝修公司來作這件事兒,站在業主的角度理解,它就是一件大事兒——「裝修公司裝修房子」,至於怎麼裝修,由裝修公司來搞定。
裝修公司一般會將這件大事兒拆分紅幾件小事兒,譬如:「室內設計」、「貼瓷磚」、「作傢俱」、「刷牆」等等,而後再去僱傭不一樣類型的工人來完成這些小事兒。
職責和類的命名應該匹配,若是在職責概括時,概括出的職責比較模糊,可能會使類的命名變得艱難。
另外,即便你概括出的職責是清晰的,若是命名與職責不符(詞不達意),仍然會給未來的維護、再重構帶來一些困難(命名是很是很是重要的)。
將多個職責被拆分到多個類時,本來在一個類中體現的職責被分散到多個類了,與此同時也須要考慮類的粒度。
粒度應當適中,粒度的控制沒有固定的標準,這須要結合業務場景具體分析。
本來用一個類就能完成的功能,如今須要結合多個類才能完成。
如今爲了確保原有的功能仍然能正常運行,較大可能會造成多個類之間的依賴關係。
若是有些類被遷移到其餘工程了,這還會涉及到工程之間的依賴關係。
「分離職責」是比較難的一個重構策略,尤爲是在一些大型項目中。
該策略若是不能良好地利用,可能會讓你的工程或解決方案變得不三不四。
若是你的重構經驗較淺,建議你從一些較小的項目練習這項重構策略。
這段代碼包含兩個類Video和Customer。
Viedo類包含三個職責:支付費用、租借Video和計算租金。
Video的職責太多,它把Customer類的職責也「搶」過來了。
public class Video { public void PayFee(decimal fee) { } public void RendVideo(Video video, Customer customer) { customer.Videos.Add(video); } public decimal CalculateBalance(Customer customer) { return customer.LateFees.Sum(); } } public class Customer { public IList<decimal> LateFees { get; set; } public IList<Video> Videos { get; set; } }
在概括職責時,咱們能夠經過識別主語的方式來肯定其歸屬。
因此,咱們能夠將Video類的PayFee()、CalculateBalance()方法放到Customer類中。
public class Video { public void RentVideo(Video video, Customer customer) { customer.Videos.Add(video); } } public class Customer { public IList<decimal> LateFees { get; set; } public IList<Video> Videos { get; set; } public void PayFee(decimal fee) { } public decimal CalculateBalance(Customer customer) { return customer.LateFees.Sum(); } }
原則就像基準線,在設計類和接口時,咱們應該儘可能遵照基準線,而不是死守基準線,在設計時不該死板地依照原則進行設計。這就比如開車,司機的視線應該始終保持在正前方,若是沿着公路上的線開車而忽視了前方的交通狀況,可能會引起交通事故。