小酌重構系列[10]——分離職責

概述

「分離職責」是常用的一個重構策略,當一個類擔任的職責太多時,應按職責將它拆分紅多個類,每一個類分別承擔「單一」的職責,也就是讓每一個類專心地作「一件事情」。html

SRP原則

在面向對象編程中,SRP原則是一個很是重要的原則(SOLID原則都很重要),在展現示例前,咱們先了解一下SRP原則是什麼,以及它有什麼做用。編程

image

什麼是SRP原則?

SRP原則的定義是這樣的:ide

There should never be more than one reason for a class to change.this

就一個類而言,應該僅有一個引發它變化的緣由。spa

爲何要遵照SRP原則?

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原則,則會帶來如下優勢:對象

  1. 類的複雜性下降:每一個類或接口都只定義單一的職責,定義清晰明確
  2. 可讀性提升:定義清晰明確,天然帶來較高的代碼可讀性
  3. 可維護性提升:代碼可讀性提升,意味着更容易理解;單一職責使得類之間的耦合性較低,更改也會較爲容易
  4. 擴展性更好:當須要擴展新的職責時,只須要定義新的接口和新的實現便可

如何遵照SRP原則?

Separating responsibility can be done by defining for every responsibility a class or an interface.blog

應該爲每一項職責定義類或接口的方式實現職責分離。接口

SRP原則的難點

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();
    }
}

 

原則就像基準線,在設計類和接口時,咱們應該儘可能遵照基準線,而不是死守基準線,在設計時不該死板地依照原則進行設計。這就比如開車,司機的視線應該始終保持在正前方,若是沿着公路上的線開車而忽視了前方的交通狀況,可能會引起交通事故。

相關文章
相關標籤/搜索