重構(gòu)
一.何謂重構(gòu)特幔?
重構(gòu):使用一系列重構(gòu)手法蓖柔,在不改變軟件可觀察行為的前提下辰企,調(diào)整其結(jié)構(gòu),提高其可理解性况鸣,降低其修改成本牢贸。
二.為什么要重構(gòu)?
- 重構(gòu)改進(jìn)軟件設(shè)計(jì)
- 重構(gòu)使軟件更容易理解
- 重構(gòu)幫助找到BUG
- 重構(gòu)提高編程速度
三.何時(shí)重構(gòu)镐捧?
- 三次法則:事不過三潜索,三則重構(gòu)
- 添加功能時(shí)重構(gòu)
- 修復(fù)BUG時(shí)重構(gòu)
- Review代碼時(shí)重構(gòu)
四.何時(shí)不該重構(gòu)?
- 無法穩(wěn)定運(yùn)行直接重寫不用重構(gòu)
- 項(xiàng)目已經(jīng)接近最后期限懂酱,不應(yīng)該重構(gòu)竹习,雖然重構(gòu)能夠提高生產(chǎn)力,但是你沒有足夠的時(shí)間列牺,這通常標(biāo)示你其實(shí)早該進(jìn)行重構(gòu)了整陌。
五.代碼的壞味道
- Duplicated Code 重復(fù)代碼
- Long Method 過長(zhǎng)函數(shù)
- Large Class 過大的類
- Long Parameter List 過長(zhǎng)參數(shù)列表
- Divergent Change 發(fā)散式變化
- Shotgun Surgery 散彈式修改
- Feature Envy 依戀情結(jié)
- Data Clumps 數(shù)據(jù)泥團(tuán)
- Primitive Obsession 基本類型偏執(zhí)
- Switch Statements switch驚悚現(xiàn)身 (使用多態(tài)性替換)
- Parallel Inheritance Hierarchies 平行繼承體系
- Lazy Class 冗贅類
- Speculative Generality 夸夸其談未來性
- Temporary Field 令人迷惑的暫時(shí)字段
- Message Chains 過渡耦合的消息鏈
- Middle Man 中間人
- Inappropriate Intimacy 狎昵關(guān)系
- Alternative Classes with Different Interfaces 異曲同工的類
- Incomplete Library Class 不完美的庫(kù)類
- Data Class 純稚的數(shù)據(jù)類
- Refused Bequest 被拒絕的遺贈(zèng)
- Comments 過多的注釋
五.常用的重構(gòu)手法
5.1 Extract Method(提煉函數(shù))
func printOwing(amount: Double) {
printBanner()
//print details
print("name:", self.name)
print("amount:", amount)
}
func printOwing(amount: Double) {
printBanner()
printDetails(amount: amount)
}
func printDetails(amount: Double) {
//print details
print("name:", self.name)
print("amount:", amount)
}
動(dòng)機(jī)
當(dāng)一個(gè)函數(shù)過長(zhǎng)或者需要注釋才能看懂的代碼,可以將這段代碼放入一個(gè)獨(dú)立的函數(shù)中,并給他一個(gè)合適的命名泌辫。
簡(jiǎn)短而命名良好的函數(shù)的優(yōu)點(diǎn):函數(shù)粒度越小随夸,它被復(fù)用的幾率越大;其次甥郑,這會(huì)使高層函數(shù)讀起來像一系列注釋逃魄;再次,如果函數(shù)都是細(xì)粒度澜搅,子類重載父類函數(shù)也會(huì)容易得多伍俘。
做法
- 創(chuàng)建一個(gè)新函數(shù),根據(jù)這個(gè)函數(shù)的意圖來給他命名(以“做什么”來命名勉躺,而不要以“怎么做命名”)癌瘾。
2.將要提煉的代碼從源函數(shù)復(fù)制到新建的目標(biāo)函數(shù)中。
3.將被提煉代碼中需要讀取的局部變量饵溅,作為參數(shù)傳給目標(biāo)函數(shù)妨退。
4.處理完所有局部變量后,進(jìn)行編譯蜕企。
5.在源函數(shù)中咬荷,將被提煉的代碼段替換為對(duì)目標(biāo)函數(shù)的調(diào)用。
6.編譯轻掩,測(cè)試幸乒。
5.2 Inline Method(內(nèi)聯(lián)函數(shù))
func getRating() -> Int {
return valueMoreThanFive() ? 2 : 1
}
func valueMoreThanFive() -> Bool {
return self.value > 5
}
func getRating() -> Int {
return (self.value > 5) ? 2 : 1
}
動(dòng)機(jī)
某些函數(shù),其內(nèi)部代碼和它的名稱一樣清晰易讀唇牧。
你手上有一群組織不合理的函數(shù)罕扎,你可以先把它們內(nèi)聯(lián)到一個(gè)大的函數(shù)里,在從中提煉出組織合理的小型函數(shù)丐重。
使用了太多的中間層腔召,使得系統(tǒng)中所有的函數(shù)只是對(duì)另外一個(gè)函數(shù)的簡(jiǎn)單調(diào)用,造成在這些調(diào)用中暈頭轉(zhuǎn)向扮惦。
做法
- 檢查函數(shù)臀蛛,確定它不具有多態(tài)性。
如果有子類重載它径缅,不要將它內(nèi)聯(lián)掺栅,因?yàn)椴荒苤剌d一個(gè)不存在的函數(shù)
2.找到這個(gè)函數(shù)所有被調(diào)用的點(diǎn)。
3.將這個(gè)函數(shù)所有被調(diào)用點(diǎn)都替換為函數(shù)本體纳猪。
4.編譯,測(cè)試桃笙。
5.刪除該函數(shù)的定義氏堤。
5.2 Move Method(搬移函數(shù))
動(dòng)機(jī)
“搬移函數(shù)”是重構(gòu)理論的支柱。如果有一個(gè)類有太多行為,或如果一個(gè)類與另一個(gè)類有太多合作而形成高度耦合鼠锈,就應(yīng)該使用搬移函數(shù)闪檬。
一個(gè)函數(shù)使用另一個(gè)對(duì)象的次數(shù)比使用自己所駐的對(duì)象還要多,考慮是否把這個(gè)函數(shù)搬移到另一個(gè)對(duì)象所屬的類中购笆。
做法
- 檢查源類中被源函數(shù)使用的一切特性(包括字段和函數(shù))粗悯,考慮他們是否也該被搬移。
如果某個(gè)特性只被你打算搬移的函數(shù)用到同欠,就應(yīng)該將它一并搬移样傍。如果有另外的函數(shù)使用了這個(gè)特性,你可以考慮將該特性的所有函數(shù)全都一并搬移铺遂。有時(shí)候衫哥,搬移一組函數(shù)比逐一搬移簡(jiǎn)單些。
- 檢查源類的子類和父類襟锐,看看是否有該函數(shù)的其它聲明撤逢。
- 在目標(biāo)類中聲明這個(gè)函數(shù)。
你可以為此函數(shù)選擇一個(gè)新名稱——對(duì)目標(biāo)類更有意義的名稱
- 將源函數(shù)的代碼復(fù)制到目標(biāo)函數(shù)中粮坞。調(diào)整目標(biāo)函數(shù)蚊荣,使其能在新家正常運(yùn)行。
- 編譯目標(biāo)類莫杈。
- 決定如何從源函數(shù)正確引用目標(biāo)對(duì)象互例。
- 修改源函數(shù),使之成為一個(gè)純委托函數(shù)姓迅。
- 編譯敲霍,測(cè)試。
- 決定是否刪除源函數(shù)丁存,或者將它作為一個(gè)委托函數(shù)保留下來肩杈。
- 如果要移除源函數(shù),將源類中對(duì)源函數(shù)的所有調(diào)用解寝,替換為對(duì)目標(biāo)函數(shù)的調(diào)用扩然。
- 編譯,測(cè)試聋伦。
5.2 Rename Method(函數(shù)改名)
動(dòng)機(jī)
函數(shù)的名稱應(yīng)該準(zhǔn)確地表達(dá)它的用途夫偶。給函數(shù)命名有一個(gè)好方法:首先考慮應(yīng)該給這個(gè)函數(shù)寫上一句怎樣的注釋,然后想辦法將注釋變成函數(shù)名稱觉增。
如果你看到一個(gè)函數(shù)名稱不能很好地表達(dá)它的用途兵拢,應(yīng)該馬上加以修改。好的函數(shù)名可以讓其它人更容易讀懂你的代碼逾礁。要想成為一個(gè)編程高手说铃,起名的水平是至關(guān)重要的。
做法
- 檢查函數(shù)是否被父類或子類實(shí)現(xiàn)過。如果是腻扇,需要針對(duì)每個(gè)實(shí)現(xiàn)分別進(jìn)行下列步驟债热。
- 聲明一個(gè)新函數(shù),將它命名為你想要的新名稱幼苛。將舊函數(shù)的代碼復(fù)制到新函數(shù)中窒篱,并進(jìn)行適當(dāng)調(diào)整。
- 編譯舶沿。
- 修改舊函數(shù)墙杯,讓它掉用新函數(shù)。
- 編譯暑椰,測(cè)試霍转。
- 找出舊函數(shù)所有被引用點(diǎn),將它們?nèi)啃薷臑閷?duì)新函數(shù)的引用一汽。每次修改后避消,編譯并測(cè)試。
- 刪除舊函數(shù)召夹。
如果舊函數(shù)是該類public接口的一部分岩喷,你可能無法安全地刪除它。這種情況下监憎,將它保留在原地纱意,并將它標(biāo)示為deprecated。
- 編譯鲸阔,測(cè)試偷霉。
5.2 Add Parameter(添加參數(shù))
為此函數(shù)添加一個(gè)對(duì)象參數(shù),讓該對(duì)象帶進(jìn)函數(shù)所需信息褐筛。
動(dòng)機(jī)
你必須修改一個(gè)函數(shù)类少,而修改后的函數(shù)需要一些過去沒有的信息,因此你需要給該函數(shù)添加一個(gè)參數(shù)渔扎。
做法
- 檢查函數(shù)是否被父類或子類實(shí)現(xiàn)過硫狞。如果是,需要針對(duì)每個(gè)實(shí)現(xiàn)分別進(jìn)行下列步驟晃痴。
- 聲明一個(gè)新函數(shù)残吩,名稱與原函數(shù)相同,只是加上新添參數(shù)倘核。將舊函數(shù)的代碼復(fù)制到新函數(shù)中泣侮。
- 編譯。
- 修改舊函數(shù)紧唱,讓它掉用新函數(shù)旁瘫。
- 編譯祖凫,測(cè)試琼蚯。
- 找出舊函數(shù)所有被引用點(diǎn)酬凳,將它們?nèi)啃薷臑閷?duì)新函數(shù)的引用。每次修改后遭庶,編譯并測(cè)試宁仔。
- 刪除舊函數(shù)。
如果舊函數(shù)是該類public接口的一部分峦睡,你可能無法安全地刪除它翎苫。這種情況下,將它保留在原地榨了,并將它標(biāo)示為deprecated煎谍。
- 編譯,測(cè)試龙屉。
5.2 Remove Parameter(移除參數(shù))
函數(shù)本體不再需要某個(gè)參數(shù)呐粘,將該參數(shù)去除
動(dòng)機(jī)
程序員可能經(jīng)常添加參數(shù)晚吞,確往往不愿意去掉它們计济。因?yàn)槎嘤嗟膮?shù)不會(huì)引起任何問題,而且以后還可能用上它胁住。
但是對(duì)于多態(tài)函數(shù)五芝,情況有所不同痘儡。可能多態(tài)函數(shù)的另一個(gè)實(shí)現(xiàn)會(huì)使用這個(gè)參數(shù)枢步,此時(shí)你就不能去除它沉删。
做法
- 檢查函數(shù)是否被父類或子類實(shí)現(xiàn)過。如果是醉途,需要針對(duì)每個(gè)實(shí)現(xiàn)分別進(jìn)行下列步驟矾瑰。
- 聲明一個(gè)新函數(shù),名稱與原函數(shù)相同结蟋,只是去除不必要的參數(shù)脯倚。將舊函數(shù)的代碼復(fù)制到新函數(shù)中。
- 編譯嵌屎。
- 修改舊函數(shù)推正,讓它調(diào)用新函數(shù)。
- 編譯宝惰,測(cè)試植榕。
- 找出舊函數(shù)所有被引用點(diǎn),將它們?nèi)啃薷臑閷?duì)新函數(shù)的引用尼夺。每次修改后尊残,編譯并測(cè)試炒瘸。
- 刪除舊函數(shù)。
如果舊函數(shù)是該類public接口的一部分寝衫,你可能無法安全地刪除它顷扩。這種情況下,將它保留在原地慰毅,并將它標(biāo)示為deprecated隘截。
- 編譯,測(cè)試汹胃。
5.2 Parameterize Method(令函數(shù)攜帶參數(shù))
若干函數(shù)做了類似的工作婶芭,但在函數(shù)名稱中卻包含了不同的值。建立單一函數(shù)着饥,以參數(shù)表達(dá)那些不同的值
動(dòng)機(jī)
你可能會(huì)發(fā)現(xiàn)兩個(gè)函數(shù)犀农,它們做著類似的工作,但因少數(shù)幾個(gè)值致使行為略有不同宰掉。
做法
- 新建一個(gè)帶參數(shù)的函數(shù)呵哨,使它可以替換先前所有的重復(fù)性函數(shù)。
- 編譯贵扰。
- 將調(diào)用舊函數(shù)的代碼改為調(diào)用新函數(shù)仇穗。
- 編譯,測(cè)試戚绕。
- 對(duì)所有舊函數(shù)重復(fù)上述步驟纹坐,每次替換后,修改并測(cè)試舞丛。
- 刪除舊函數(shù)耘子。
如果舊函數(shù)是該類public接口的一部分,你可能無法安全地刪除它球切。這種情況下谷誓,將它保留在原地,并將它標(biāo)示為deprecated吨凑。
- 編譯捍歪,測(cè)試。
5.2 Replace Parameter with Explicit Methods(以明確函數(shù)取代參數(shù))
你有一個(gè)函數(shù)鸵钝,其中完全根據(jù)參數(shù)值不同而采取不同的行為糙臼。針對(duì)該參數(shù)的每一個(gè)可能值,建立一個(gè)獨(dú)立函數(shù)
重構(gòu)前:
func setValue(name: String, value: CGFloat) {
if name == "height" {
self.height = value
} else if name == "width" {
self.width = value
}
}
重構(gòu)后:
func setHeight(value: CGFloat) {
self.height = value
}
func setWidth(value: CGFloat) {
self.width = value
}
動(dòng)機(jī)
如果某個(gè)參數(shù)有多種可能的值恩商,而函數(shù)內(nèi)又以條件表達(dá)式檢查這些參數(shù)值变逃,并根據(jù)不同參數(shù)值做出不同的行為,那么就應(yīng)該使用本項(xiàng)重構(gòu)怠堪。
接口更清晰揽乱,相比之下名眉,Switch.setOn()比Switch.setState(true)要清楚得多。
做法
- 針對(duì)參數(shù)的每一種可能值凰棉,新建一個(gè)明確函數(shù)损拢。
- 修改條件表達(dá)式的每個(gè)分支,使其調(diào)用合適的新函數(shù)渊啰。
- 修改每個(gè)分支后探橱,編譯并測(cè)試。
- 修改原函數(shù)的每一個(gè)被調(diào)用點(diǎn)绘证,讓它們調(diào)用合適的新函數(shù)。
- 編譯哗讥,測(cè)試嚷那。
- 所有調(diào)用端都修改完畢后,刪除原函數(shù)杆煞。
5.2 Preserve Whole Objcet(保持對(duì)象完整)
你把某個(gè)對(duì)象的若干值作為函數(shù)調(diào)用時(shí)的參數(shù)魏宽,改為傳遞整個(gè)對(duì)象
重構(gòu)前:
let low = daysTempRange.low
let high = daysTempRange.high
withinPlan = plan.withinRange(low, high)
重構(gòu)后:
withinPlan = plan.withinRange(daysTempRange)
動(dòng)機(jī)
有時(shí)候,你會(huì)將同一對(duì)象的若干數(shù)據(jù)項(xiàng)作為參數(shù)决乎,傳遞給某個(gè)函數(shù)队询。這樣做的問題在于:萬(wàn)一將來被調(diào)用函數(shù)需要新的數(shù)據(jù)項(xiàng),你就必須查找并修改對(duì)此函數(shù)的所有調(diào)用构诚。
使參數(shù)列表變短蚌斩,提高代碼可讀性。
弊端:如果你傳的是數(shù)值范嘱,被調(diào)用的函數(shù)只依賴于這些數(shù)值送膳。如果你傳遞的是整個(gè)對(duì)象,被調(diào)用的函數(shù)所在的對(duì)象就需要依賴參數(shù)對(duì)象丑蛤。
5.2 Replace Parameter with Methods(以函數(shù)取代參數(shù))
對(duì)象調(diào)用某個(gè)函數(shù)叠聋,并將所得結(jié)果作為參數(shù),傳遞給另一個(gè)函數(shù)受裹。而另一個(gè)函數(shù)本身也能調(diào)用前一個(gè)函數(shù)碌补。讓參數(shù)接受者去除該項(xiàng)參數(shù),并直接調(diào)用前一個(gè)函數(shù)
重構(gòu)前:
let basePrice = quantity * itemPrice
let discountLevel = self.getDiscountLevel()
let finalPrice = discountedPrice(basePrice, discountLevel)
重構(gòu)后:
let basePrice = quantity * itemPrice
let finalPrice = discountedPrice(basePrice)
動(dòng)機(jī)
如果函數(shù)可以通過其它途徑獲得參數(shù)值棉饶,那么它就不應(yīng)該通過參數(shù)取得該值厦章。
做法
- 如果有必要,使用Extract Method將參數(shù)的計(jì)算過程提煉到一個(gè)獨(dú)立函數(shù)中砰盐。
- 將函數(shù)本地內(nèi)引用該參數(shù)的地方改為調(diào)用新建的函數(shù)闷袒。
- 每次替換后,編譯并測(cè)試岩梳。
- 全部替換完成后囊骤,使用Remove Parameter將該參數(shù)去掉晃择。
5.2 Introduce Parameter Object(引入?yún)?shù)對(duì)象)
某些參數(shù)總是很自然地同時(shí)出現(xiàn)
動(dòng)機(jī)
你常會(huì)看到特定的一組參數(shù)總是一起被傳遞,可以運(yùn)用一個(gè)對(duì)象包裝所有這些數(shù)據(jù)也物。
當(dāng)你把這些參數(shù)組織到一起后宫屠,往往很快可以發(fā)現(xiàn)一些行為可被移至新建的類中。
做法
- 新建一個(gè)類滑蚯,用以表現(xiàn)你想替換的一組參數(shù)浪蹂。
- 編譯。
- 針對(duì)使用該組參數(shù)的所有函數(shù)告材,使用Add Parameter坤次,傳入新建類的實(shí)例對(duì)象,并將此參數(shù)值設(shè)置為null斥赋。
- 對(duì)于這些參數(shù)中的每一項(xiàng)缰猴,從函數(shù)名中移除掉,并修改調(diào)用端和函數(shù)本體疤剑,讓它們都通過新的參數(shù)對(duì)象取得該值滑绒。
- 每去除一個(gè)參數(shù),編譯并測(cè)試隘膘。
- 將原先的參數(shù)全部去掉之后疑故,觀察有無適當(dāng)?shù)暮瘮?shù)可以運(yùn)用Move Method搬移到參數(shù)對(duì)象之中。
被搬移的可以是整個(gè)函數(shù)弯菊,也可以是某個(gè)函數(shù)的部分代碼纵势。如果是后者,需要先用Extract Method將該部分代碼提煉到一個(gè)新的函數(shù)中误续。
5.2 Hide Method(隱藏函數(shù))
有一個(gè)函數(shù)吨悍,從來沒有被其他任何類用到,將這個(gè)函數(shù)修改為private
動(dòng)機(jī)
一個(gè)類蹋嵌,暴露給其他類的接口越多育瓜,越容易產(chǎn)生耦合。在一個(gè)函數(shù)確實(shí)需要被其他類訪問之前栽烂,應(yīng)該讓它對(duì)其他類不可見躏仇。
做法
- 經(jīng)常檢查有沒有可能降低某個(gè)函數(shù)的可見度。
- 盡可能降低所有函數(shù)的可見度腺办。
- 每完成一組函數(shù)的隱藏之后焰手,編譯并測(cè)試。
5.2 Replace Constructor With Factory Method(以工廠函數(shù)取代構(gòu)造函數(shù))
你希望在創(chuàng)建對(duì)象時(shí)不僅僅是簡(jiǎn)單的構(gòu)建動(dòng)作怀喉,將構(gòu)造函數(shù)替換為工廠函數(shù)
重構(gòu)前:
Employee(type: Int) {
self.type = type
}
重構(gòu)后:
static func create(type: Int) -> Employee {
return Employee(type: type)
}
動(dòng)機(jī)
最明顯的動(dòng)機(jī)就是书妻,在派生子類的過程中以工廠函數(shù)取代類型碼。
做法
- 新建一個(gè)工廠函數(shù)躬拢,讓它調(diào)用現(xiàn)有的構(gòu)造函數(shù)躲履。
- 將調(diào)用構(gòu)造函數(shù)的代碼改為調(diào)用工廠函數(shù)见间。
- 每次替換后,編譯并測(cè)試工猜。
- 將構(gòu)造函數(shù)聲明為private米诉。
- 編譯。
5.2 Pull Up Field(字段上移)
兩個(gè)子類擁有相同的字段篷帅,將該字段移至父類
動(dòng)機(jī)
如果子類是分別開發(fā)的史侣,或者是在重構(gòu)過程中組合起來的,你常會(huì)發(fā)現(xiàn)它們擁有重復(fù)性魏身,特別是字段更容易重復(fù)惊橱。如果它們重復(fù)了,你就可以將它們歸納到父類中去叠骑。
做法
- 針對(duì)待提升的字段李皇,檢查它們的所有被使用點(diǎn),確認(rèn)它們以同樣的方式被使用宙枷。
- 如果這些字段的名稱不同,先將它們改名茧跋,使每一個(gè)名字都和你想為父類字段取的名字相同慰丛。
- 在父類中新建一個(gè)字段,并移除子類中的字段瘾杭。
4.編譯诅病,測(cè)試。
5.2 Pull Up Method(函數(shù)上移)
有些函數(shù)粥烁,在各個(gè)子類中產(chǎn)生完全相同的結(jié)果贤笆。將該函數(shù)移至超類中。
動(dòng)機(jī)
如果函數(shù)在各個(gè)子類中的函數(shù)體都相同讨阻,這就是最顯而易見的適用場(chǎng)合芥永。
Pull Up Mehod常常緊隨其它重構(gòu)而被使用。也許你能找出若干個(gè)身處不同子類中的函數(shù)钝吮,而它們又可以通過其它重構(gòu)手法調(diào)整成為相同的函數(shù)埋涧。這時(shí)候,你就可以先調(diào)整函數(shù)奇瘦,然后再將它們上移到父類中去棘催。當(dāng)然,如果你足夠自信耳标,也可以一次完成這兩個(gè)步驟醇坝。
做法
- 檢查待上移的函數(shù),確定它們是完全一致的次坡。
- 如果待上移的函數(shù)的名字不同呼猪,先使用Rename Method將它們都修改為你想要在父類中使用的名字画畅。
- 在父類中新建一個(gè)函數(shù),將某一個(gè)待上移函數(shù)的代碼復(fù)制到其中郑叠,做適當(dāng)?shù)恼{(diào)整夜赵,然后編譯。
- 移除一個(gè)待上移的子類函數(shù)乡革, 編譯寇僧,測(cè)試。
- 逐一移除待上移的子類函數(shù)沸版,直到只剩下父類中的函數(shù)為止嘁傀。每次移除之后都需要測(cè)試。
- 觀察該函數(shù)的調(diào)用者视粮,看看是否可以改為使用父類類型的對(duì)象细办。
5.2 Pull Up Constructor Body(構(gòu)造函數(shù)本體上移)
在父類中新建一個(gè)構(gòu)造函數(shù),并在子類構(gòu)造函數(shù)中調(diào)用它
重構(gòu)前:
class Manager: Employee {
init(name: String, id: String, grade: Int) {
self.name = name
self.id = id
self.grade = grade
}
}
重構(gòu)后:
class Employee {
init(name: String, id: String) {
self.name = name
self.id = id
}
...
}
class Manager: Employee {
init(name: String, id: String, grade: Int) {
self.grade = grade
super.init(name: name, id: id)
}
...
}
動(dòng)機(jī)
如果你看到各個(gè)子類中的函數(shù)有共同行為蕾殴,第一個(gè)念頭就是將共同行為提煉到一個(gè)獨(dú)立函數(shù)中笑撞,然后將這個(gè)函數(shù)上移到父類。對(duì)于構(gòu)造函數(shù)而言钓觉,它們彼此的共同行為往往就是“對(duì)象的構(gòu)建”茴肥。
做法
- 在父類中定義一個(gè)構(gòu)造函數(shù)。
- 將子類構(gòu)造函數(shù)中的共同代碼搬移到父類構(gòu)造函數(shù)中荡灾。
- 將子類構(gòu)造函數(shù)中的共同代碼刪掉瓤狐,改成調(diào)用新建的父類構(gòu)造函數(shù)。
- 編譯批幌,測(cè)試础锐。
5.2 Pull Down Method(函數(shù)下移)
父類中的某個(gè)函數(shù)只與部分子類有關(guān),將這個(gè)函數(shù)移到相關(guān)子類去
動(dòng)機(jī)
Pull Down Method與Pull Up Method剛好相反荧缘。當(dāng)我們有必要把某些行為從父類移至子類時(shí)皆警,就使用Pull Down Method。使用Extract SubClass之后你可能會(huì)需要它胜宇。
做法
- 在所有子類中聲明該函數(shù)耀怜,將父類中的函數(shù)本體復(fù)制到每一個(gè)子類函數(shù)中。
- 刪除父類中的函數(shù)桐愉。
- 編譯财破,測(cè)試。
- 將該函數(shù)從所有不需要它的那些子類中刪掉从诲。
- 編譯左痢,測(cè)試。
5.2 Pull Down Field(字段下移)
父類中的某個(gè)字段只被部分子類用到,將這個(gè)字段移到需要它的那些子類中去
動(dòng)機(jī)
與Pull Up Field剛好相反俊性。如果只有部分子類需要父類中的一個(gè)字段略步,可以使用本項(xiàng)重構(gòu)。
做法
- 在所有子類中聲明該字段定页。
- 將該字段從父類中移除趟薄。
- 編譯,測(cè)試典徊。
- 將該字段從所有不需要它的那些子類中刪掉杭煎。
- 編譯,測(cè)試卒落。
5.2 Extract Subclass(提煉子類)
類中的某些特性只被某些實(shí)例用到羡铲,新建一個(gè)子類,將那一部分特性移到子類中
動(dòng)機(jī)
與Pull Up Field剛好相反儡毕。如果只有部分子類需要父類中的一個(gè)字段也切,可以使用本項(xiàng)重構(gòu)。
做法
- 在所有子類中聲明該字段腰湾。
- 將該字段從父類中移除雷恃。
- 編譯,測(cè)試费坊。
- 將該字段從所有不需要它的那些子類中刪掉褂萧。
- 編譯,測(cè)試葵萎。
5.2 Extract Superclass(提煉父類)
兩個(gè)類有相似的特性。為兩個(gè)類建立一個(gè)父類唱凯,將相同特性移至父類羡忘。
動(dòng)機(jī)
重復(fù)代碼是系統(tǒng)中最糟糕的東西之一。如果你在不同的地方做同一件事情磕昼,一旦需要修改那些動(dòng)作卷雕,你就需要修改每一份代碼。
如果兩個(gè)類以相同的方式做類似的事情票从,你就需要使用繼承機(jī)制來去除這些重復(fù)漫雕。
做法
- 為原本的類創(chuàng)建一個(gè)空白父類。
- 運(yùn)用Pull Up Filed峰鄙、Pull Up Method浸间、 Pull Up Constructor Body逐一將子類中的共同元素上移到父類。
- 每次上移后吟榴,編譯并測(cè)試魁蒜。
- 檢查留在子類中的函數(shù),看它們是否還有共通成分。如果有兜看,可以使用Extract Method將共同部分提煉出來锥咸,然后使用Pull Up Method將提煉出的函數(shù)上移到父類中。
- 將所有共通元素上移到父類后细移,檢查子類的所有用戶搏予。如果它們只使用了共同接口,你就可以把調(diào)用它們的對(duì)象類型改為父類類型弧轧。
5.2 Replace Inheritance with Delegation(以委托代替繼承)
在子類中新建一個(gè)字段持有父類的對(duì)象雪侥;調(diào)整子類函數(shù),令它改而委托父類劣针;然后去掉兩者的繼承關(guān)系
動(dòng)機(jī)
設(shè)計(jì)模式中講到校镐,優(yōu)先使用組合而不是繼承。
但是你常常會(huì)遇到這樣的情況:一開始繼承了一個(gè)類捺典,隨后發(fā)現(xiàn)父類中的許多操作并不真正適用于子類鸟廓。這是你可以使用組合來代替繼承。
做法
- 在子類中新建一個(gè)字段襟己,使其引用父類的一個(gè)對(duì)象引谜,并將它初始化為self。
- 修改子類中的所有函數(shù)擎浴,讓它們不再使用父類员咽,轉(zhuǎn)而使用上面新加的那個(gè)字段。
- 去除兩個(gè)類之間的繼承關(guān)系贮预,新建一個(gè)受托類的對(duì)象賦給受托字段贝室。
- 對(duì)于父類中的每一個(gè)函數(shù),在子類中添加一個(gè)簡(jiǎn)單的委托函數(shù)仿吞。
- 編譯滑频,測(cè)試。
5.2 Extract Class(提煉類)
某個(gè)類做了應(yīng)該由兩個(gè)類做的事唤冈。建一個(gè)新類峡迷,將相關(guān)字段和和函數(shù)搬移到新類。
動(dòng)機(jī)
單一職責(zé)原則說到你虹,一個(gè)類應(yīng)該只有一個(gè)職責(zé)绘搞。
如果一個(gè)類職責(zé)過于多,過于復(fù)雜傅物,就應(yīng)該把它們分離到一個(gè)單獨(dú)的類中夯辖。
做法
- 決定如果分解類所負(fù)的責(zé)任。
- 建立一個(gè)新類挟伙,用以表現(xiàn)從舊類中分離出的職責(zé)楼雹。
- 建立從舊類訪問新類的連接關(guān)系模孩。(大部分情況是讓舊類持有新類類型的變量)
- 對(duì)于每一個(gè)你想搬移的字段,使用Move Filed進(jìn)行搬移贮缅。
- 每次搬移后編譯榨咐,測(cè)試。
- 使用Move Method將必要函數(shù)搬移至新類谴供。先搬較低層的函數(shù)块茁,后搬較高層的函數(shù)。
- 每次搬移后編譯桂肌,測(cè)試数焊。
- 檢查,精簡(jiǎn)每個(gè)類的接口崎场。
- 決定是否公開新類佩耳。如果你確實(shí)需要公開它,就需要決定讓它成為引用類型還是值類型谭跨。
六干厚、重構(gòu)示例(心動(dòng)日常查看距離功能重構(gòu))
心動(dòng)日常iOS的查看距離會(huì)根據(jù)用戶所處的位置使用合適的地圖,如果雙方都在國(guó)內(nèi)使用高德地圖螃宙,如果有一方在國(guó)外使用蘋果地圖蛮瞄。
舊版本雙方都有位置信息時(shí)才會(huì)展示地圖,新版本接到新的需求:默認(rèn)展示地圖谆扎,只要一方有位置信息在地圖上對(duì)應(yīng)的位置展示人物頭像挂捅,人物頭像上增加用戶位置權(quán)限標(biāo)識(shí)。
接到需求后堂湖,我先熟悉代碼闲先。經(jīng)過一段時(shí)間的熟悉,我發(fā)現(xiàn)了一些問題:
1.控制器類持有MAMapView和YLAppleMapView類的對(duì)象无蜂,他們的功能類似并且是互斥的饵蒂,可以抽象出相同的接口。
2.一些實(shí)例變量和函數(shù)很明顯應(yīng)該封裝到地圖類內(nèi)部酱讶,而不是放在ViewController類里。
3.需要調(diào)用對(duì)應(yīng)地圖類對(duì)象時(shí)彼乌,都要使用if語(yǔ)句來判斷
因?yàn)檫@些問題泻肯,如果我要修改現(xiàn)有代碼,我必須要修改對(duì)應(yīng)的高德地圖和蘋果的圖部分的代碼慰照。但是因?yàn)樗麄兯诤瘮?shù)的函數(shù)名和具體的業(yè)務(wù)邏輯可能很不一致灶挟,找起來可能會(huì)很麻煩。
如果我要往地圖上添加頭像毒租,需要判斷當(dāng)前是什么地圖稚铣,并分別調(diào)用不同的方法。想想就很頭大,于是我決定重構(gòu)惕医。
重構(gòu)前后代碼結(jié)構(gòu)對(duì)比:
具體重構(gòu)過程通過git代碼演示耕漱。