《重構(gòu)》學(xué)習(xí)筆記(05)-- 在對(duì)象之間搬移特性

在對(duì)象設(shè)計(jì)的過程中,“決定把責(zé)任放在哪兒”是最重要的事情之一。但無論使用對(duì)象技術(shù)多么嫻熟,也無法保證在設(shè)計(jì)對(duì)象時(shí)一次做對(duì)。因此,需要進(jìn)行重構(gòu),改變原有的設(shè)計(jì)。

Move Method(搬移函數(shù))

簡單的說就是將一個(gè)類中的函數(shù)搬移到另一個(gè)類中。


image

重構(gòu)后


image

這種重構(gòu)方式看似簡單,其中的難點(diǎn)在于確定是否應(yīng)該移動(dòng)一個(gè)函數(shù)。通常,我們的做法如下:
  • 檢查源類中被原函數(shù)使用的一切特性(包括字段和函數(shù)),考慮它們是否也該被搬移。
  • 檢查源類中的子類和超類,看看是否有函數(shù)的其他聲明。
  • 在目標(biāo)類中聲明這個(gè)函數(shù)。
  • 將源函數(shù)的代碼復(fù)制到目標(biāo)函數(shù)中。調(diào)整后者,使其能夠正常運(yùn)行。
  • 編譯目標(biāo)類。
  • 決定如何從源函數(shù)正確引用目標(biāo)對(duì)象。
  • 決定是否刪除源函數(shù),或者將它當(dāng)做一個(gè)委托函數(shù)保留下來。
  • 如果要移除源函數(shù),將對(duì)所有源函數(shù)的所有調(diào)用,替換為對(duì)目標(biāo)函數(shù)的調(diào)用。
  • 編譯,測試。

Move Field(搬移字段)

Move Field與Move Method類似,將一個(gè)字段搬移到更適合的類中。


image

重構(gòu)后


image

隨著系統(tǒng)的發(fā)展,你會(huì)發(fā)現(xiàn)你需要新的類,并將現(xiàn)有的工作責(zé)任拖到新類中。通常我們的做法為:
  • 如果字段的訪問級(jí)是public,使用Encapsulate Field(封裝字段)將它封裝起來。
  • 在目標(biāo)類中建立與源字段相同的字段,并同時(shí)建立相應(yīng)的設(shè)值、取值函數(shù)。
  • 決定如何在源對(duì)象中引用目標(biāo)對(duì)象。
  • 刪除源字段。
  • 將所有對(duì)源字段的引用替換為對(duì)某個(gè)目的函數(shù)的調(diào)用。

Extract Class(提煉類)

如果某個(gè)類做了應(yīng)該由兩個(gè)類做的事。那么建立一個(gè)新類,將相關(guān)的字段和函數(shù)從舊類搬移到新類。


image

重構(gòu)后


image

一個(gè)類應(yīng)該是一個(gè)清楚的抽象,處理一些明確的責(zé)任。有些類含有大量的函數(shù)和數(shù)據(jù),這樣的類往往太大而不易理解,此時(shí)需要考慮哪些部分可以分離出去,并將它們分離到一個(gè)單獨(dú)的類中。如果子類化只影響類的部分特性,或某些特性需要以一種方式來子類化,某些特性則需要以另一種方式子類化,這就意味需要分解原來的類。做法:
  • 決定如何分解類所負(fù)的責(zé)任。
  • 建立一個(gè)新類,用以表現(xiàn)從舊類中分離出來的責(zé)任。
  • 建立從舊類訪問新類的連接關(guān)系。
  • 運(yùn)用Mvoe Field搬移每個(gè)需要搬移的字段。
  • 運(yùn)用Move Method搬移必要的函數(shù)。先搬移較低層函數(shù),再搬移高層函數(shù)。
  • 決定是否要公開新類。

Inline Class(將類內(nèi)聯(lián)化)

如果某個(gè)類沒有做太多事情,可以將這個(gè)類中所有的特性搬移到另一個(gè)類中,然后移除原類。


image

重構(gòu)后


image

做法:
  • 在目標(biāo)類身上聲明源類的public協(xié)議,并將其中所有函數(shù)委托至源類。
  • 修改所有源類引用點(diǎn),改而引用目標(biāo)類。
  • 編譯、測試。
  • 運(yùn)用Move Method和Move Field,將源類的特性全部搬移到目標(biāo)類。

Hide Delegate(隱藏“委托關(guān)系”)

客戶類通過一個(gè)委托類調(diào)用另一個(gè)對(duì)象,在服務(wù)類上建立客戶所需的所有函數(shù),用以隱藏委托關(guān)系。


image

重構(gòu)為
[圖片上傳失敗...(image-b9ec27-1560092577298)]
做法為:

  • 對(duì)于每一個(gè)委托關(guān)系中的函數(shù),在服務(wù)對(duì)象端建立一個(gè)簡單的委托函數(shù)。
  • 調(diào)整客戶,令它只調(diào)用服務(wù)對(duì)象提供的函數(shù)。
  • 每次調(diào)整后,編譯并測試。
  • 如果將來不再有任何客戶需要取用Delegate(受托類),便可移除服務(wù)對(duì)象中的相關(guān)訪問函數(shù)。
  • 編譯、測試。

Remove Middle Man(移除中間人)

移除中間人與Hide Delegate是逆操作。如果某個(gè)類做了過多的簡單委托動(dòng)作,那么讓客戶直接調(diào)用受委托類。


image

重構(gòu)為


image

委托的代價(jià)是,委托對(duì)象的變化會(huì)引起服務(wù)器對(duì)象變化。當(dāng)服務(wù)類完成成了委托類的中間人,那么請(qǐng)刪除中間人吧!不斷使用Hide Delegate和Move Middle Man調(diào)整程序結(jié)構(gòu)。做法為:
  • 建立函數(shù),用以獲得受托對(duì)象。
  • 對(duì)于每個(gè)委托函數(shù),在服務(wù)類中刪除該函數(shù),并讓需要調(diào)用該函數(shù)的客戶轉(zhuǎn)為調(diào)用受托對(duì)象。
  • 處理每個(gè)委托函數(shù)后,編譯、測試。

Introduce Foreign Method(引入外加函數(shù))

如果你正在使用一個(gè)類,但是你需要一項(xiàng)新的服務(wù),這個(gè)類卻無法修改或無法供應(yīng)。此時(shí)你應(yīng)該在客戶類中新建一個(gè)函數(shù),并以第一參數(shù)形式傳入一個(gè)服務(wù)類的實(shí)例。
例如,我們需要獲取一個(gè)Date的下一天。

Date newStart = new Date(previousEnd.getYear(),
                     previousEnd.getMonth(),previousEnd.getDate()+1);

可以重構(gòu)為:

Date newStart = nextDay(previousEnd);
private static Date nextDay(Date arg){
    return new Date(arg.getYear(), arg.getMonth(),arg.getDate()+1);
}

在進(jìn)行本項(xiàng)重構(gòu)時(shí), 如果你以外加函數(shù)實(shí)現(xiàn)一項(xiàng)功能, 那就是一個(gè)明確信號(hào): 這個(gè)函數(shù)原本應(yīng)該在提供服務(wù)的類中實(shí)現(xiàn).但是不要忘記: 外加函數(shù)終歸是權(quán)宜之計(jì). 如果不能把外加函數(shù)搬移到服務(wù)類中, 就把外加函數(shù)交給服務(wù)類的持有者, 請(qǐng)他幫你在服務(wù)類中實(shí)現(xiàn)這個(gè)函數(shù).本項(xiàng)重構(gòu)一般的做法為:

  • 在客戶類中建立一個(gè)函數(shù),用來提供你需要的功能。
    =》這個(gè)函數(shù)不應(yīng)該調(diào)用客戶類的任何特性。如果它需要一個(gè)值,把該值當(dāng)作參數(shù)傳給它。
  • 以服務(wù)類實(shí)例作為該函數(shù)的第一個(gè)參數(shù)。

Introduce Local Extension(引入本地?cái)U(kuò)展)

如果你需要為服務(wù)類提供一些額外函數(shù),但你無法修改這個(gè)類。那么建立一個(gè)新的類,使它包含這些額外函數(shù),讓這個(gè)擴(kuò)展品成為源類的子類或者包裝類。


image

重構(gòu)為


image

例如以Date類為例,如果我們需要擴(kuò)展,我們可以使用子類:
class MfDateSub extends Date{
    public MfDateSub nextDay()...
    public int dayOfYear()...
}

或者使用擴(kuò)展類

class MfDateWrap{
    private Date _original;
}

常用的做法:

  • 建立一個(gè)擴(kuò)展類,將它作為原始類的子類或者包裝類。
  • 在擴(kuò)展類中加入轉(zhuǎn)型構(gòu)造函數(shù)。
  • 在擴(kuò)展類中加入新特性。
  • 根據(jù)需要,將原始對(duì)象替換為擴(kuò)展對(duì)象。
  • 將針對(duì)原始類定義的所有外加函數(shù)版移到擴(kuò)展類中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《重構(gòu)》讀書筆記 總覽 第一部分 第一章從實(shí)例程序出發(fā),展示設(shè)計(jì)的缺陷,對(duì)其重構(gòu)可以了解重構(gòu)的過程和方法。 第二部...
    白樺葉閱讀 2,528評(píng)論 2 5
  • chapter 1 重構(gòu),第一個(gè)案例 1.1 什么時(shí)候需要重構(gòu) 需要為程序添加一個(gè)特性,但代碼結(jié)構(gòu)無法使自己方便的...
    VictorBXv閱讀 2,220評(píng)論 0 1
  • 在對(duì)對(duì)象的設(shè)計(jì)過程中,“決定把責(zé)任放在哪兒”即使不是最重要的事,也是最重要的事情之一。 1 Move Method...
    hklbird閱讀 586評(píng)論 0 1
  • 一. Move Method(搬移函數(shù)) 介紹 場景你的程序中,有個(gè)函數(shù)與其所駐類之外的另一個(gè)類進(jìn)行更多交流:調(diào)用...
    nimw閱讀 431評(píng)論 0 0
  • 一,重構(gòu),第一個(gè)案例 這一章作者先用一個(gè)影片出租程序的案例,來演示重構(gòu)的過程 每個(gè)Customer顧客可以租多部M...
    高稷閱讀 11,077評(píng)論 1 19

友情鏈接更多精彩內(nèi)容