在對(duì)象設(shè)計(jì)的過程中,“決定把責(zé)任放在哪兒”是最重要的事情之一。但無論使用對(duì)象技術(shù)多么嫻熟,也無法保證在設(shè)計(jì)對(duì)象時(shí)一次做對(duì)。因此,需要進(jìn)行重構(gòu),改變原有的設(shè)計(jì)。
Move Method(搬移函數(shù))
簡單的說就是將一個(gè)類中的函數(shù)搬移到另一個(gè)類中。
重構(gòu)后
這種重構(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è)字段搬移到更適合的類中。
重構(gòu)后
隨著系統(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ù)從舊類搬移到新類。
重構(gòu)后
一個(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è)類中,然后移除原類。
重構(gòu)后
做法:
- 在目標(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)系。
重構(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)用受委托類。
重構(gòu)為
委托的代價(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ò)展品成為源類的子類或者包裝類。
重構(gòu)為
例如以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ò)展類中。