重構(gòu) - 改善既有代碼的設(shè)計(jì)
1 重構(gòu),第一個(gè)示例
- 重構(gòu)前筝尾,先檢查自己是否有一套可靠的測(cè)試集。這些測(cè)試必須有自我驗(yàn)證能力办桨。TDD
- 重構(gòu)技術(shù)就是以微小的步伐修改程序筹淫。如果犯下錯(cuò)誤,很容易便可發(fā)現(xiàn)它呢撞。
- 傻瓜都能寫出計(jì)算機(jī)可以理解的代碼损姜。唯有能寫出人類容易理解的代碼的,才是優(yōu)秀的程序員殊霞。
- 編程時(shí)薛匪,需要遵循營(yíng)地法則:保證你離開(kāi)時(shí),代碼庫(kù)一定比來(lái)的時(shí)候更健康脓鹃。
- 好代碼驗(yàn)證的標(biāo)準(zhǔn)是人們是否能輕而易舉的修改它逸尖。
2 重構(gòu)的原則
2.1 何謂重構(gòu)
- 重構(gòu):對(duì)軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變可觀察行為的前提下瘸右,提高其可理解性娇跟,降低其修改成本。
- 重構(gòu)的關(guān)鍵在于運(yùn)用大量小且保證軟件行為的步驟太颤,一步步達(dá)到大規(guī)模的修改苞俘。
- 如果有人說(shuō)他們的代碼在重構(gòu)過(guò)程中有1-2天時(shí)間不可用,基本上可以確定龄章,他們?cè)谧龅氖虏皇侵貥?gòu)吃谣。
2.2 兩頂帽子
- 添加新功能乞封,可能需要優(yōu)化之前的程序結(jié)構(gòu);當(dāng)功能開(kāi)發(fā)好岗憋,也需要優(yōu)化下程序的結(jié)構(gòu)肃晚。不同的角色切換,是在添加功能過(guò)程中必不可少的步驟仔戈。
2.3 為何重構(gòu)
- 改進(jìn)軟件的設(shè)計(jì)
- 使軟件更容易理解
- 幫助開(kāi)發(fā)者找到bug
- 提高編程速度
2.4 何時(shí)重構(gòu)
- 預(yù)備性:讓添加新功能更容易
- 幫助理解:使代碼更易懂
- 撿垃圾式重構(gòu)
- 有計(jì)劃和見(jiàn)機(jī)行事的重構(gòu):骯臟的代碼必須重構(gòu)关串,但漂亮的代碼也需要很多重構(gòu)
- 長(zhǎng)期重構(gòu)
- 復(fù)審代碼時(shí)重構(gòu)
- 何時(shí)不應(yīng)該重構(gòu):
- 只有當(dāng)需要理解其工作原理時(shí)
- 如果重寫比重構(gòu)容易
2.5 重構(gòu)的挑戰(zhàn)
- 緩解新功能開(kāi)發(fā)
- 重構(gòu)的唯一目的就是讓我們開(kāi)發(fā)更快,用更少的工作量創(chuàng)造更大的價(jià)值
- 重構(gòu)應(yīng)該總是由經(jīng)濟(jì)利益驅(qū)動(dòng)监徘,而不是在于把代碼庫(kù)打磨得閃閃發(fā)光
- 分支
- 持續(xù)集成晋修,也叫基于主干開(kāi)發(fā),避免任何分支彼此差距太大凰盔,從而降低合并的難度
- 測(cè)試
- 自測(cè)試代碼:快速發(fā)現(xiàn)錯(cuò)誤
- 遺留代碼
- 重構(gòu)可以很好地幫助我們理解遺留系統(tǒng)墓卦,但遺留的系統(tǒng)大多數(shù)是沒(méi)有測(cè)試。解決辦法是:沒(méi)測(cè)試就加測(cè)試户敬。書籍推薦《修改代碼的藝術(shù)》
2.6 重構(gòu)落剪、架構(gòu)和YAGNI
- 一旦代碼寫出來(lái),架構(gòu)就固定了山叮,只會(huì)因?yàn)槌绦騿T的草率對(duì)待而逐漸腐敗,重構(gòu)可以改變這個(gè)狀態(tài)
- 重構(gòu)可以應(yīng)對(duì)未來(lái)的需求變化
2.7 重構(gòu)與軟件開(kāi)發(fā)過(guò)程
- 極限編程是最早的敏捷軟件開(kāi)發(fā)方法之一添履。要真正以敏捷的方式運(yùn)作項(xiàng)目屁倔,團(tuán)隊(duì)成員必須在重構(gòu)上有能力、有熱情暮胧,他們采用的開(kāi)發(fā)過(guò)程必須與常規(guī)的锐借、持續(xù)的重構(gòu)相匹配
- 自測(cè)試代碼 -> 持續(xù)集成 -> 重構(gòu)
2.8 重構(gòu)與性能
- 除了對(duì)性能有嚴(yán)格要求的實(shí)時(shí)系統(tǒng),其他情況下往衷,“編寫快速軟件”的秘訣是:先寫出可調(diào)優(yōu)的代碼钞翔,然后調(diào)優(yōu)它以求獲得足夠的速度
- 短期看,重構(gòu)可能會(huì)讓軟件變慢席舍,但它的優(yōu)化階段的軟件性能調(diào)優(yōu)更容易布轿,最終還是會(huì)得到好的效果
2.9 重構(gòu)起源何處
- 優(yōu)秀的程序員肯定會(huì)花一些時(shí)間來(lái)清理自己的代碼,因?yàn)樗麄兇_定自己幾乎無(wú)法一開(kāi)始就寫出整潔的代碼
2.10 自動(dòng)化重構(gòu)
- 強(qiáng)大的IDE會(huì)讓重構(gòu)變得很輕松
3 代碼的壞味道
- 神秘命名:如果想不出一個(gè)好的名字来颤,說(shuō)明背后很可能隱藏著更深的設(shè)計(jì)問(wèn)題
- 重復(fù)代碼:優(yōu)化:對(duì)比差異汰扭,提取相同。
- 過(guò)長(zhǎng)函數(shù):優(yōu)化:條件福铅、循環(huán)萝毛、公共集中的過(guò)程提取處理
- 過(guò)長(zhǎng)參數(shù)列表:優(yōu)化:使用對(duì)象合并參數(shù)
- 全局?jǐn)?shù)據(jù):優(yōu)化:合并數(shù)據(jù)到方法、類成員中
- 可變數(shù)據(jù):優(yōu)化:函數(shù)式編程滑黔、數(shù)據(jù)永不改變
- 發(fā)散式變化:做出的某個(gè)模塊的小修改笆包,必須修改某個(gè)類的多個(gè)函數(shù)环揽。優(yōu)化:每次只關(guān)心一個(gè)上下文,將聯(lián)動(dòng)的改變提取處理
- 霰彈式修改:每次遇到變化庵佣,都必須在很多不同的類內(nèi)做出許多小修改歉胶。優(yōu)化:提取公共方法
- 依戀情結(jié):如果一個(gè)函數(shù)跟另一個(gè)模塊中的函數(shù)或者數(shù)據(jù)交流格外頻繁,遠(yuǎn)勝于在自己所處的模塊內(nèi)部交流
- 模塊化:力求將代碼分出區(qū)域秧了,最大化區(qū)域內(nèi)部的交互跨扮、最小化跨區(qū)域的交互。所謂高內(nèi)聚验毡,低耦合
- 數(shù)據(jù)泥團(tuán):兩個(gè)類中相同的字段衡创、許多函數(shù)簽名中相同的參數(shù)。優(yōu)化:提取公共字段為一個(gè)類晶通、對(duì)象
- 基本類型偏執(zhí):一些基本類型無(wú)法表示一個(gè)數(shù)據(jù)的真實(shí)意義璃氢,例如電話號(hào)碼、溫度等狮辽。優(yōu)化:使用類一也、對(duì)象字符串類型變量取代基本類型
- 重復(fù)的
switch
:優(yōu)化:使用策略模式、提取子類 - 循環(huán)語(yǔ)句:同過(guò)長(zhǎng)函數(shù)
- 冗贅的元素:優(yōu)化:內(nèi)聯(lián)喉脖、刪除
- 夸夸其談通用性:優(yōu)化:內(nèi)聯(lián)椰苟、刪除
- 臨時(shí)字段:優(yōu)化:內(nèi)聯(lián)、刪除
- 過(guò)長(zhǎng)的消息鏈:優(yōu)化:減少委托關(guān)系
- 中間人:優(yōu)化:用繼承替代代理委托
- 內(nèi)幕交易:優(yōu)化:合并相同的聯(lián)系树叽,提取不同的成分
- 過(guò)大的類:優(yōu)化:提取類舆蝴、子類、接口
- 異曲同工的類:優(yōu)化:提取公共類题诵、使用子類繼承
- 純數(shù)據(jù)類:它們擁有一些字段洁仗,以及訪問(wèn)、讀寫這些字段的函數(shù)性锭。優(yōu)化:將相關(guān)操作封裝進(jìn)去赠潦,降低
public
成員變量 - 被拒絕的遺贈(zèng):如果子類繼承超類的數(shù)據(jù)和方法,但不使用草冈。優(yōu)化:用內(nèi)聯(lián)數(shù)據(jù)和方法她奥、代理委托替代繼承關(guān)系
- 注釋:當(dāng)你感覺(jué)需要編寫注釋時(shí),請(qǐng)先嘗試重構(gòu)怎棱,試著讓所有注釋變得多余
4 構(gòu)筑測(cè)試體系
4.1 自測(cè)試代碼的價(jià)值
- 程序員編寫代碼的時(shí)間僅占所有時(shí)間中很少的一部分方淤,但是花費(fèi)在調(diào)試上的時(shí)間是最多的。修復(fù)bug通常是比較快的蹄殃,但找出bug所在卻是一場(chǎng)噩夢(mèng)
- 確保所有測(cè)試都是完全自動(dòng)化携茂,讓他們檢查自己的測(cè)試結(jié)果
- 一套測(cè)試就是一個(gè)強(qiáng)大的bug偵探器,能夠大大縮減查找bug所需的時(shí)間
4.2 測(cè)試代碼示例
4.3 第一個(gè)測(cè)試
- 總是確保測(cè)試不該通過(guò)時(shí)诅岩,會(huì)產(chǎn)生失敗
- 頻繁地運(yùn)行測(cè)試讳苦,對(duì)于你正在處理的代碼與其對(duì)應(yīng)的測(cè)試至少每隔幾分鐘就要運(yùn)行一次带膜,每天至少運(yùn)行一次所有的測(cè)試
4.4 再添加一個(gè)測(cè)試
- 編寫為臻完善的測(cè)試并經(jīng)常運(yùn)行,好過(guò)對(duì)完美測(cè)試的無(wú)盡等待
- 保持每個(gè)測(cè)試用例獨(dú)立性鸳谜,避免產(chǎn)生共享對(duì)象膝藕。因?yàn)闇y(cè)試之間會(huì)通過(guò)共享產(chǎn)生交互,而測(cè)試的結(jié)果就會(huì)受測(cè)試運(yùn)行次序的影響咐扭,導(dǎo)致測(cè)試結(jié)果的不確定性
- 例子
change todescribe('province', () => { const shanghai = new Province('shanghai'); it('shortfall', () => { expect(shanghai.shortfall).equal(5) }) })
describe('province', () => { let shanghai = null; beforeEach(() => { shanghai = new Province('shanghai'); }) it('shortfall', () => { expect(shanghai.shortfall).equal(5) }) })
4.5 修改測(cè)試夾具
- 配置 - 檢查 - 驗(yàn)證
- 準(zhǔn)備 - 行為 - 斷言
4.6 探測(cè)邊界條件
- 考慮可能出錯(cuò)的邊界條件芭挽,把測(cè)試火力集中在那兒
- 不要因?yàn)闇y(cè)試無(wú)法捕捉所有的bug就不寫測(cè)試,因?yàn)闇y(cè)試的確可以捕捉到大多數(shù)bug
- 任何測(cè)試都不能證明一個(gè)程序沒(méi)有bug
- 當(dāng)測(cè)試數(shù)量達(dá)到一定程度后蝗肪,繼續(xù)增加測(cè)試代理的邊際效用會(huì)遞減
- 應(yīng)該把測(cè)試集中在可能出錯(cuò)的地方袜爪,觀察代碼,看哪兒變得復(fù)雜薛闪、哪些地方可能出錯(cuò)
4.7 測(cè)試遠(yuǎn)不止如此
- 一個(gè)架構(gòu)的好壞辛馆,很大程度上要取決于它的可測(cè)試性,這是一個(gè)好的行業(yè)趨勢(shì)
- 每當(dāng)收到bug報(bào)告豁延,請(qǐng)先寫一個(gè)單元測(cè)試來(lái)暴露這個(gè)bug
- 一個(gè)測(cè)試集是否夠好昙篙,最好的衡量標(biāo)準(zhǔn)其實(shí)是主觀的,試問(wèn)自己:如果有人在代碼里引入了一個(gè)缺陷诱咏,自己有多大的自信它能被測(cè)試集發(fā)現(xiàn)
5 介紹重構(gòu)名錄
6 第一組重構(gòu)
6.1 提煉函數(shù)
對(duì)立:內(nèi)聯(lián)函數(shù)
目的:將意圖與實(shí)現(xiàn)分開(kāi)苔可。意圖 == 主干;實(shí)現(xiàn) == 分支的實(shí)現(xiàn)
場(chǎng)景:如果需要花時(shí)間瀏覽一段代碼才能弄清它到底干什么袋狞,那么就應(yīng)該將其提煉到一個(gè)函數(shù)中焚辅,并根據(jù)它所做的事為其命名。以后再讀到這段代碼時(shí)硕并,可以一眼就能知道函數(shù)的用途法焰,大多數(shù)根本不需要關(guān)心函數(shù)如何實(shí)現(xiàn)秧荆。
-
例子:
function printOwing(invoice){ printBanner(); const outstanding = calculateOutstanding(); //print details console.info('name:', invoice.name); console.info('amount:', outstanding); }
function printOwing(invoice){ printBanner(); const outstanding = calculateOutstanding(); printDetails(outstanding, invoice); function printDetails(){ console.info('name:', invoice.name); console.info('amount:', outstanding); } }
6.2 內(nèi)聯(lián)函數(shù)
對(duì)立:提煉函數(shù)
目的:去除不必要間接層/委托層倔毙,降低系統(tǒng)復(fù)雜度
-
場(chǎng)景:
- 一堆不合理的函數(shù),可以將其內(nèi)聯(lián)到一個(gè)大型函數(shù)中乙濒,再重新提煉到小函數(shù)中
- 代碼太多間接層陕赃,使系統(tǒng)中的所有函數(shù)都似乎只是對(duì)另一個(gè)函數(shù)簡(jiǎn)單的委托
-
例子:
function getRating(driver){ return moreThanFiveDeliveries(driver) ? 2 : 1; } function moreThanFiveDeliveries(driver){ return driver.deliveries > 5; }
function getRating(driver){ return driver.deliveries > 5 ? 2 : 1; }
6.3 提煉變量
對(duì)立:內(nèi)聯(lián)變量
目的:將復(fù)雜的表達(dá)式使用變量說(shuō)明
-
例子:
return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100);
const basePrice = order.quantity * order.itemPrice; const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; const shipping = Math.min(basePrice * 0.1, 100); return basePrice - quantityDiscount + shipping;
6.4 內(nèi)聯(lián)變量
對(duì)立:提煉變量
目的:去除不必要變量
-
場(chǎng)景:
- 表達(dá)式比變量更有表現(xiàn)力
-
例子:
const basePrice = order.basePrice; return basePrice > 25;
return order.basePrice > 25;
6.5 改變函數(shù)聲明
目的:好名字能讓人一眼看出函數(shù)的用途,而不必看代碼實(shí)現(xiàn)
-
使用:
- 先寫一句注釋描述這個(gè)函數(shù)的用途颁股,再把這句注釋變成函數(shù)的名字
-
例子:
function calc(){}
function calcOrder(){}
6.6 封裝變量
-
目的:
- 重構(gòu)數(shù)據(jù)轉(zhuǎn)移為重構(gòu)函數(shù)么库,更易于處理
- 監(jiān)控?cái)?shù)據(jù)的變化
場(chǎng)景:如果數(shù)據(jù)的可訪問(wèn)范圍大
-
例子:
let defaultOwner = {};
let defaultOwner = {}; export function defaultOwner(){ return defaultOwner; } export function getDefaultOwner(arg){ defaultOwner = arg; }
6.7 變量改名
目的:好名字可讓上下文更清晰
-
例子:
const a = height * width;
const area = height * width;
6.8 引入?yún)?shù)對(duì)象
目的:組織數(shù)據(jù)結(jié)構(gòu),讓數(shù)據(jù)項(xiàng)之間的關(guān)系更清晰甘有,參數(shù)列表也能縮短
場(chǎng)景:一個(gè)函數(shù)接受多個(gè)參數(shù)
-
例子:
function invoice(startDate, endDate){} function received(starDate, endDate){}
function invoice(dateRange){} function received(dateRanges){}
6.9 函數(shù)組合成類
-
目的:
- 對(duì)象內(nèi)部調(diào)用這些函數(shù)可以少傳參數(shù)诉儒,從而簡(jiǎn)化函數(shù)調(diào)用,而且一個(gè)對(duì)象可更方便傳遞給系統(tǒng)的其他部分
- 客戶端修改對(duì)象的核心數(shù)據(jù)亏掀,通過(guò)計(jì)算得出的派生數(shù)據(jù)會(huì)自動(dòng)與核心數(shù)據(jù)保存一致
場(chǎng)景:如果一組函數(shù)形影不離地操作同一塊數(shù)據(jù)(通常是將這塊數(shù)據(jù)作為參數(shù)傳遞給函數(shù))
-
例子:
function base(reading){} function taxableCharge(reading){} function calcBaseCharge(reading){}
class Reading{ base(){} taxableCharge(){} calcBaseCharge(){} }
6.10 函數(shù)組合成變換
-
目的:
- 增強(qiáng)數(shù)據(jù)忱反,將數(shù)據(jù)邏輯統(tǒng)一在一個(gè)地方處理
- 高內(nèi)聚
場(chǎng)景:需要把數(shù)據(jù)放到另一個(gè)程序中運(yùn)行泛释,計(jì)算出各種派生信息
-
例子:
function base(reading){} function taxableCharge(reading){}
function enrichReading(arg){ const reading = _.cloneDeep(arg); reading.baseCharge = base(reading); reading.taxableCharge = taxableCharge(reading); return reading; }
6.11 拆分階段
目的:保證單一原則,一段代碼只做一件事
場(chǎng)景:如果一段代碼同時(shí)處理兩件或者更多不同的事情
-
例子:
const orderArr = orderStr.split(/\s+/); const productPrice = priceList(order[0].split('-')[1]); const orderPrice = parseInt(orderArr[1]) * productPrice;
const orderRecord = parseOrder(order); const orderPrice = price(orderRecord, priceList); function parseOrder(str){ const values = str.split(/\s+/); return { priceId: values[0].split('-')[1], quantity: parseInt(values[1]) }; } function price(order, priceList){ return order.quantity * priceList[order.productId]; }
7 封裝
7.1 封裝記錄
-
目的:
- 對(duì)象可以隱藏結(jié)構(gòu)的細(xì)節(jié)
- 有助于字段改名
場(chǎng)景:對(duì)于可變數(shù)據(jù)
-
例子:
const organization = {name: 'John', country: 'GB'};
class Organization{ constructor(data){ this._name = data.name; this._country = data.country; } get name(){ return this._name; } set name(arg){ this._name = arg; } get country(){ return this._country; } set country(arg){ this._country = arg; } }
7.2 封裝集合
目的:控制外界對(duì)類中集合的訪問(wèn)權(quán)温算,避免集合被直接修改
場(chǎng)景:類中集合為可變數(shù)據(jù)
-
例子:
class Person{ get courses(){ return this._courses; } set courses(list){ this._course = list; } }
class Person{ get course(){ return this._courses.slice(); } addCourse(course){} removeCourse(course){} }
7.3 以對(duì)象取代基本類型
目的:擴(kuò)展或者增強(qiáng)數(shù)據(jù)的行為
場(chǎng)景:如果數(shù)據(jù)項(xiàng)需要更多的含義或者行為時(shí)
-
例子:
orders.filter(o => 'high' === o.priority || 'rush' === o.priority);
orders.filter(o => o.priority.higherThan(new Priority('normal')));
7.4 以查詢?nèi)〈R時(shí)變量
-
目的:
- 新函數(shù)與原函數(shù)之間的邊界更清晰
- 便于代碼抽離
- 利于函數(shù)復(fù)用
場(chǎng)景:那些值被計(jì)算一次且之后不再被修改的變量
-
例子:
const basePrice = this._quantity * this._itemPrice; if(basePrice > 1000){ return basePrice * 0.95; }else{ return basePrice * 0.98; }
get basePrice(){ this._quantity * this._itemPrice; } if(this.basePrice > 1000){ return this.basePrice * 0.95; }else{ return this.basePrice * 0.98; }
7.5 提取類
對(duì)立:內(nèi)聯(lián)類
目的:將大類分成小類
場(chǎng)景:如果維護(hù)一個(gè)大量函數(shù)和數(shù)據(jù)的類
-
例子:
class Person{ get officeAreaCode(){ return this._officeAreaCode; } get officeNumber(){ return this._officeNumber; } }
class Person{ get officeAreaCode(){ return this._telephoneNumber.areaCode; } get officeNumber(){ return this._telephoneNumber.number; } } class TelephoneNumber{ get areaCode(){ return this._areaCode; } get number(){ return this._number; } }
7.6 內(nèi)聯(lián)類
對(duì)立:提煉類
目的:減少不必要的類
-
場(chǎng)景:
- 如果一個(gè)類不再承擔(dān)足夠的責(zé)任
- 重新分類兩個(gè)類的不同職責(zé)
-
例子:
class Person{ get officeAreaCode(){ return this._telephoneNumber.areaCode; } get officeNumber(){ return this._telephoneNumber.number; } } class TelephoneNumber{ get areaCode(){ return this._areaCode; } get number(){ return this._number; } }
class Person{ get officeAreaCode(){ return this._officeAreaCode; } get officeNumber(){ return this._officeNumber; } }
7.7 隱藏委托關(guān)系
對(duì)立:移除中間人
目的:每個(gè)模塊盡可能減少了解系統(tǒng)的其他部分怜校,把部分依賴關(guān)系隱藏起來(lái),減少調(diào)用者雙方了解更多細(xì)節(jié)
場(chǎng)景:如果被調(diào)方接口頻繁修改時(shí)
-
例子:
const manager = person.department.manager;
const manager = person.manager; class Person{ get manager(){ return this.department.manager; } }
7.8 移除中間人
對(duì)立:隱藏委托關(guān)系
目的:減少不不必要的委托
場(chǎng)景:如果過(guò)多的轉(zhuǎn)發(fā)函數(shù)沒(méi)有讓程序本身提升的擴(kuò)展性注竿,就應(yīng)刪除部分委托
-
例子:
const manager = person.manager; class Person{ get manager(){ return this.department.manager; } }
const manager = person.department.manager;
7.9 替換算法
目的:用簡(jiǎn)單算法處理
場(chǎng)景:隨著對(duì)業(yè)務(wù)不斷深入茄茁,發(fā)覺(jué)有更簡(jiǎn)單的算法實(shí)現(xiàn)
-
例子:
function foundPerson(people){ for(let i = 0; i < people.length; i++){ if(people[i] === 'John'){ return 'John'; } if(people[i] === 'Maria'){ return 'Maria'; } if(people[i] === 'Mike'){ return 'Mike'; } } return ''; }
function foundPerson(people){ const candidate = ['John', 'Maria', 'Mike']; return people.find(p => candidate.includes(p)) || ''; }
8 搬移特性
8.1 搬移函數(shù)
目的:減少對(duì)不常用函數(shù)的外部依賴,增加常用函數(shù)的內(nèi)部依賴巩割,高內(nèi)聚
場(chǎng)景:如果一個(gè)方法頻繁調(diào)用別處的一個(gè)函數(shù)裙顽,并且被頻繁調(diào)用的函數(shù)在該上下文關(guān)系不大時(shí)
-
例子:
class Account{ get overdraftCharge(){} }
class AccountType{ get overdraftCharge(){} }
將內(nèi)聚放在一個(gè)類中,改名喂分。
變量為名詞
方法為動(dòng)詞
8.2 搬移字段
目的:高內(nèi)聚锦庸,將不屬于當(dāng)前類的屬性搬到另一個(gè)類中
場(chǎng)景:如果修改一條記錄時(shí),總是需要同時(shí)改動(dòng)另一個(gè)記錄
-
例子:
class Customer{ get plan(){ return this._plan; } get disountRate(){ return this._discountRate; } }
class Customer{ get plan(){ return this._plan; } get disountRate(){ return this.plan.discountRate; } }
8.3 搬移語(yǔ)句到函數(shù)
對(duì)立:搬移語(yǔ)句到調(diào)用者
目的:消除重復(fù)蒲祈,提取公共內(nèi)容甘萧,即表現(xiàn)一致的行為
場(chǎng)景:發(fā)現(xiàn)調(diào)用某個(gè)函數(shù)時(shí),總有一些相同的代碼也需要每次執(zhí)行
-
例子:
result.push(`<p>title: ${person.photo.title}</p>`); result.concat(photoData(person.photo)); function photoData(photo){ return { `<p>location: ${photo.location}</p>`; `<p>date: ${photo.date}</p>`; } }
result.concat(photoData(person.photo)); function photoData(photo){ return { `<p>title: ${person.photo.title}</p>`; `<p>location: ${photo.location}</p>`; `<p>date: ${photo.date}</p>`; } }
8.4 搬移語(yǔ)句到調(diào)用者
對(duì)立:搬移語(yǔ)句到函數(shù)
目的:將可變的行為搬移到調(diào)用者內(nèi)部
場(chǎng)景:以往多個(gè)地方公共的行為梆掸,如今需要在某些調(diào)用點(diǎn)表現(xiàn)不同的行為扬卷,并且調(diào)用點(diǎn)與調(diào)用者之間的邊界差別不大。如果差別較大的酸钦,只能重新設(shè)計(jì)
-
例子:
emitPhotoData(outStream, person.photo); function emitPhotoData(outStream, photo){ outStream.write(`<p>title: ${photo.title}</p>`); outStream.write(`<p>title: ${photo.location}</p>`); }
emitPhotoData(outStream, person.photo); outStream.write(`<p>title: ${photo.title}</p>`); function emitPhotoData(outStream, photo){ outStream.write(`<p>title: ${photo.location}</p>`); }
8.5 以函數(shù)調(diào)用取代內(nèi)聯(lián)代碼
目的:消除重復(fù)
場(chǎng)景:如果一些內(nèi)聯(lián)代碼做的事情是已有函數(shù)可以做到的
-
例子:
let hasMa = false; for(const i of states){ if(i === 'MA'){ hasMa = true; } }
const hasMa = states.includes('MA');
8.6 移動(dòng)語(yǔ)句
目的:高內(nèi)聚內(nèi)部代碼
場(chǎng)景:如果有幾行代碼取用了同一個(gè)數(shù)據(jù)結(jié)構(gòu)怪得,那么最好讓它們?cè)谝黄?/p>
-
例子:
const pricePlan = retrievePricingPlan(); const order = retreiveOrder(); let charge; const chargePerUnit = pricingPlan.unit();
const pricePlan = retrievePricingPlan(); const chargePerUnit = pricingPlan.unit(); const order = retreiveOrder(); let charge;
8.7 拆分循環(huán)
目的:保持循環(huán)內(nèi)部只做一件事
場(chǎng)景:如果循環(huán)內(nèi)部身兼多職
-
例子:
let averageAge = 0; let totalSalary = 0; for(const p of people){ averageAge += p.age; totalSalary += p.salary; } averageAge = averageAge / people.length;
let averageAge = 0; for(const p of people){ averageAge += p.age; } let totalSalary = 0; for(const p of people){ averageAge += p.age; } averageAge = averageAge / people.length;
先進(jìn)行重構(gòu),在進(jìn)行性能優(yōu)化卑硫。將代碼變得清晰徒恋,對(duì)后期的擴(kuò)展、優(yōu)化欢伏,都極其方便
8.8 以管道取代循環(huán)
目的:提高代碼可讀性
場(chǎng)景:如果代碼的邏輯可以通過(guò)內(nèi)置方法處理
-
例子:
const names = []; for(const i of input){ if(i.job === 'programmer'){ names.push(i.name); } }
const names = input .filter(i => i.job === 'programmer') .map(i => i.name);
8.9 移除死代碼
- 目的:移除無(wú)用代碼
9 重新組織數(shù)據(jù)
9.1 拆分代碼
目的:每個(gè)變量只承擔(dān)一個(gè)責(zé)任入挣,同一個(gè)變量承擔(dān)兩件不同的事情,會(huì)令代碼閱讀者糊涂
場(chǎng)景:大多數(shù)情況下變量只賦值一次硝拧,除了:循環(huán)變量(例如:
for(let i =0; i < 5; i++)
中的i
)径筏,收集結(jié)果變量-
例子:
let temp = 2 * (height + width); temp = height * width;
const perimeter = 2 * (height * width); const area = height * width;
變量聲明可以剛開(kāi)始聲明為
const
,如果發(fā)覺(jué)需要重復(fù)賦值障陶,再改為let
9.2 字段改名
目的:好的名字可以幫助閱讀者更易理解
-
例子:
class Organization{ get name(){} }
class Organization{ get title(){} }
9.3 以查詢?nèi)〈缮兞?/h3>
目的:減少方法中的副作用滋恬,單一原則
場(chǎng)景:對(duì)數(shù)據(jù)的修改常常導(dǎo)致代碼的各個(gè)部分以丑陋的形式互相耦合:在一處修改數(shù)據(jù),卻在另一處造成難以發(fā)現(xiàn)的破壞
-
例子:
get discountTotal(){
return this._discountTotal;
}
set discount(number){
const old = this._discount;
this._discount = number;
this._discountTotal += old - number;
}
get discountTotal(){
return this._baseTotal - this._discount;
}
get discount(){
return this._discount;
}
set discount(number){
this._discount = number;
}
9.4 將引用對(duì)象改為值對(duì)象
對(duì)立:將值對(duì)象改為引用對(duì)象
目的:值對(duì)象的不可變性處理起來(lái)更容易抱究,可以任意的傳遞恢氯,防止被外部修改
場(chǎng)景:如果不需要改變值的引用關(guān)系,每個(gè)值是不可變的
-
例子:
class Product{
applyDiscount(arg){
this._price -= arg;
}
}
class Product{
applyDiscount(arg){
this._price = new Money(this._price.amount - arg, this._price.currency);
}
}
9.5 將值對(duì)象改為引用對(duì)象
目的:保持?jǐn)?shù)據(jù)共享
場(chǎng)景:如果數(shù)據(jù)結(jié)構(gòu)中包含多個(gè)記錄,而這些記錄都有關(guān)聯(lián)到同一個(gè)邏輯的數(shù)據(jù)結(jié)構(gòu)勋拟,例如一個(gè)數(shù)據(jù)的改動(dòng)遏暴,需要共享到整個(gè)數(shù)據(jù)集
-
例子:
let customer = new Customer(customerData);
let customer = customerRepository.get(customerData, id);
10 簡(jiǎn)化條件邏輯
10.1 分解條件表達(dá)式
目的:簡(jiǎn)化條件,易于理解代碼邏輯
場(chǎng)景:當(dāng)檢查處理邏輯復(fù)雜時(shí)
-
例子:
if(!date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd)){
charge = quantity * plan.summerRate;
}else{
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
const isSummer = !date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd);
function summerCharge(){
return quantity * plan.summerRate;
}
function regularCharge(){
return quantity * plan.regularRate + plan.regularServiceCharge;
}
charge = isSummer() ? summerCharge() : regularCharge();
10.2 合并條件表達(dá)式
目的:統(tǒng)一處理?xiàng)l件語(yǔ)句
場(chǎng)景:當(dāng)檢查條件各不相同指黎,最終行為一致時(shí)
-
例子:
if(age < 18) return 'younger';
if(experience < 5) return 'younger';
if(!isPassTest) return 'younger';
if(isYounger()) return 'younger';
function isYounger(){
return age<18 || experience < 5 || !isPassTest;
}
10.3 衛(wèi)語(yǔ)句取代嵌套條件表達(dá)式
目的:減少邏輯的復(fù)雜度
場(chǎng)景:當(dāng)出現(xiàn)需要單獨(dú)檢查某個(gè)特定條件時(shí)
-
例子:
function getPayment(){
let result;
if(isRead){
result = deadAmount();
}else{
if(isSeparated){
result = separatedAmount();
}else{
if(isRetired){
result = retiredAmount();
}else{
result = normalAmount();
}
}
}
}
function getPayment(){
if(isRead) return deadAmount();
if(isSeparated) return separatedAmount();
if(isRetired) return retiredAmount();
return normalAmount();
}
10.4 以多態(tài)取代條件表達(dá)式
目的:增強(qiáng)擴(kuò)展性朋凉,減少邏輯的復(fù)雜度
場(chǎng)景:當(dāng)多個(gè)邏輯處理情況
-
例子:
switch(bird.type){
case 'EuropeanSwallow':
return 'EuropeanSwallow';
case 'AfricanSwallow':
return 'AfricanSwallow';
default:
return 'unknown';
}
class EuropeanSwallow{
get name(){
return 'EuropeanSwallow';
}
}
class AfricanSwallow{
get name(){
return 'AfricanSwallow';
}
}
10.5 引入特例
目的:提供復(fù)用性以及統(tǒng)一性
場(chǎng)景:如果某部分邏輯都在檢查某個(gè)特殊值,并且處理的邏輯也都相同
-
例子:
if(customer === 'unknown'){
customerName = 'occupant';
}
class UnknownCustomer{
get name(){
return 'occupant';
}
}
10.6 引入斷言
目的:保障傳入值是可預(yù)測(cè)的醋安,預(yù)習(xí)發(fā)現(xiàn)測(cè)試的BUG
-
例子:
if(this.discountRate){
base = base - this.discountRate * base;
}
asset(this.discountRate>0);
if(this.discountRate){
base = base - this.discountRate * base;
}
11 重構(gòu)API
11.1 查詢函數(shù)和修改函數(shù)分離
目的:減少函數(shù)副作用 == 任何有返回值的函數(shù)杂彭,都要減少它的副作用
場(chǎng)景:當(dāng)函數(shù)中又有查詢,又有命令
-
例子:
function getTotalOutStandingAndSendBill(person){
const result = customer.invoice.reduce((total, each)=>each.amount + total, 0);
sendBill();
return result;
}
function getTotalStanding(person){
return customer.invoice.reduce((total, each)=>each.amount + total, 0);
}
function sendBill(){}
function handler(){
const totalOutstanding = getTotalStanding();
sendBill();
}
11.2 函數(shù)參數(shù)化
目的:增強(qiáng)函數(shù)的功能
場(chǎng)景:如果發(fā)現(xiàn)多個(gè)函數(shù)邏輯相似吓揪,只有某1-2個(gè)字面量不同
-
例子:
function tenPercentRaise(person){
person.salary = person.salary.multiply(1.1);
}
function fivePercentRaise(person){
person.salary = person.salary.multiply(0.05);
}
function raise(person, factor){
person.salary = person.salary.multiply(factor);
}
11.3 移除標(biāo)記參數(shù)
目的:代碼更清晰亲怠,減少函數(shù)的復(fù)雜度
場(chǎng)景:如果參數(shù)值影響函數(shù)內(nèi)部的控制流
-
例子:
function setDimension(name, value){
if(name === 'height'){}
if(name === 'width'){}
}
function setHeight(value){
this._height = value;
}
function setWidth(value){
this._width = value;
}
11.4 保持對(duì)象完整性
目的:縮短參數(shù)列表,參數(shù)配置靈活
場(chǎng)景:如果傳入多個(gè)參數(shù)傳值
-
例子:
const{low, high} = temperature;
if(isValidTemperature(low, high)){};
if(isValidTemperature(temperature)){};
11.5 以查詢?nèi)〈鷧?shù)
對(duì)立:以參數(shù)取代查詢
目的:減少傳參柠辞,從而減少調(diào)用者的成本
場(chǎng)景:如果傳入多個(gè)參數(shù)团秽,并且從一個(gè)參數(shù)推導(dǎo)出另一個(gè)參數(shù)
-
例子:
availableVacation(employee, employee.grade);
function availableVacation(employee, grade){}
availableVacation(employee);
function availableVacation(employee){
const {grade} = employee;
}
11.6 以參數(shù)取代查詢
對(duì)立:以查詢?nèi)〈鷧?shù)
目的:減少函數(shù)的副作用,以及引用關(guān)系叭首,保持函數(shù)的純凈度
場(chǎng)景:如果函數(shù)引用了一個(gè)全局變量习勤,或者引用想移除的元素
-
例子:
const weather ={};
targetTemperature(plan);
function targetTemperature(plan){
const{curTemperature} = weather;
}
const weather ={};
targetTemperature(plan, weather);
function targetTemperature(plan, weather){
const{curTemperature} = weather;
}
11.7 移除設(shè)置函數(shù)
目的:防止某字段被修改
場(chǎng)景:當(dāng)不希望某個(gè)字段被修改時(shí)
-
例子:
class Person{
get id(){}
set id(name){}
}
class Person{
get id(){}
}
11.8 以工廠函數(shù)取代構(gòu)造函數(shù)
目的:增加靈活性
場(chǎng)景:不存在繼承關(guān)系時(shí)
-
例子:
const leadEngineer = new Employee('name','E');
const leadEngineer = createEngineer('name');
11.9 以命令取代函數(shù)
對(duì)立:以函數(shù)取代命令
目的:命令對(duì)象提供更大的靈活性,并且還可以支持撤銷焙格、生命周期的管理等附加操作
場(chǎng)景:當(dāng)普通函數(shù)無(wú)法提供強(qiáng)有力靈活性
-
例子:
function score(candidate, media){
let result = 0;
let healthLevel = 0;
}
function hasPassMedicalExam(){}
class Scorer{
constructor(candidate, medicalExam){
this._candidate = candidate;
this._medicalExam = medicalExam;
}
execute(){
let result = 0;
let healthLevel = 0;
}
hasPassMedicalExam(){}
}
11.10 以函數(shù)取代命令
對(duì)立:以命令取代函數(shù)
目的:函數(shù)簡(jiǎn)單化
場(chǎng)景:大多數(shù)情況下图毕,只想調(diào)用一個(gè)函數(shù),完成自己的工作眷唉,不需要函數(shù)那么復(fù)雜
-
例子:
class ChargeCalculator{
constructor(customer, usage){
this._customer = customer;
this._usage = usage;
}
execute(){
return this._customer.rate * this._usage;
}
}
function charge(customer, usage){
return customer.rate * usage;
}
12 處理繼承關(guān)系
目的:減少方法中的副作用滋恬,單一原則
場(chǎng)景:對(duì)數(shù)據(jù)的修改常常導(dǎo)致代碼的各個(gè)部分以丑陋的形式互相耦合:在一處修改數(shù)據(jù),卻在另一處造成難以發(fā)現(xiàn)的破壞
例子:
get discountTotal(){
return this._discountTotal;
}
set discount(number){
const old = this._discount;
this._discount = number;
this._discountTotal += old - number;
}
get discountTotal(){
return this._baseTotal - this._discount;
}
get discount(){
return this._discount;
}
set discount(number){
this._discount = number;
}
對(duì)立:將值對(duì)象改為引用對(duì)象
目的:值對(duì)象的不可變性處理起來(lái)更容易抱究,可以任意的傳遞恢氯,防止被外部修改
場(chǎng)景:如果不需要改變值的引用關(guān)系,每個(gè)值是不可變的
例子:
class Product{
applyDiscount(arg){
this._price -= arg;
}
}
class Product{
applyDiscount(arg){
this._price = new Money(this._price.amount - arg, this._price.currency);
}
}
目的:保持?jǐn)?shù)據(jù)共享
場(chǎng)景:如果數(shù)據(jù)結(jié)構(gòu)中包含多個(gè)記錄,而這些記錄都有關(guān)聯(lián)到同一個(gè)邏輯的數(shù)據(jù)結(jié)構(gòu)勋拟,例如一個(gè)數(shù)據(jù)的改動(dòng)遏暴,需要共享到整個(gè)數(shù)據(jù)集
例子:
let customer = new Customer(customerData);
let customer = customerRepository.get(customerData, id);
目的:簡(jiǎn)化條件,易于理解代碼邏輯
場(chǎng)景:當(dāng)檢查處理邏輯復(fù)雜時(shí)
例子:
if(!date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd)){
charge = quantity * plan.summerRate;
}else{
charge = quantity * plan.regularRate + plan.regularServiceCharge;
}
const isSummer = !date.isBefore(plan.summerStart) && !date.isAfter(plan.summerEnd);
function summerCharge(){
return quantity * plan.summerRate;
}
function regularCharge(){
return quantity * plan.regularRate + plan.regularServiceCharge;
}
charge = isSummer() ? summerCharge() : regularCharge();
目的:統(tǒng)一處理?xiàng)l件語(yǔ)句
場(chǎng)景:當(dāng)檢查條件各不相同指黎,最終行為一致時(shí)
例子:
if(age < 18) return 'younger';
if(experience < 5) return 'younger';
if(!isPassTest) return 'younger';
if(isYounger()) return 'younger';
function isYounger(){
return age<18 || experience < 5 || !isPassTest;
}
目的:減少邏輯的復(fù)雜度
場(chǎng)景:當(dāng)出現(xiàn)需要單獨(dú)檢查某個(gè)特定條件時(shí)
例子:
function getPayment(){
let result;
if(isRead){
result = deadAmount();
}else{
if(isSeparated){
result = separatedAmount();
}else{
if(isRetired){
result = retiredAmount();
}else{
result = normalAmount();
}
}
}
}
function getPayment(){
if(isRead) return deadAmount();
if(isSeparated) return separatedAmount();
if(isRetired) return retiredAmount();
return normalAmount();
}
目的:增強(qiáng)擴(kuò)展性朋凉,減少邏輯的復(fù)雜度
場(chǎng)景:當(dāng)多個(gè)邏輯處理情況
例子:
switch(bird.type){
case 'EuropeanSwallow':
return 'EuropeanSwallow';
case 'AfricanSwallow':
return 'AfricanSwallow';
default:
return 'unknown';
}
class EuropeanSwallow{
get name(){
return 'EuropeanSwallow';
}
}
class AfricanSwallow{
get name(){
return 'AfricanSwallow';
}
}
目的:提供復(fù)用性以及統(tǒng)一性
場(chǎng)景:如果某部分邏輯都在檢查某個(gè)特殊值,并且處理的邏輯也都相同
例子:
if(customer === 'unknown'){
customerName = 'occupant';
}
class UnknownCustomer{
get name(){
return 'occupant';
}
}
目的:保障傳入值是可預(yù)測(cè)的醋安,預(yù)習(xí)發(fā)現(xiàn)測(cè)試的BUG
例子:
if(this.discountRate){
base = base - this.discountRate * base;
}
asset(this.discountRate>0);
if(this.discountRate){
base = base - this.discountRate * base;
}
目的:減少函數(shù)副作用 == 任何有返回值的函數(shù)杂彭,都要減少它的副作用
場(chǎng)景:當(dāng)函數(shù)中又有查詢,又有命令
例子:
function getTotalOutStandingAndSendBill(person){
const result = customer.invoice.reduce((total, each)=>each.amount + total, 0);
sendBill();
return result;
}
function getTotalStanding(person){
return customer.invoice.reduce((total, each)=>each.amount + total, 0);
}
function sendBill(){}
function handler(){
const totalOutstanding = getTotalStanding();
sendBill();
}
目的:增強(qiáng)函數(shù)的功能
場(chǎng)景:如果發(fā)現(xiàn)多個(gè)函數(shù)邏輯相似吓揪,只有某1-2個(gè)字面量不同
例子:
function tenPercentRaise(person){
person.salary = person.salary.multiply(1.1);
}
function fivePercentRaise(person){
person.salary = person.salary.multiply(0.05);
}
function raise(person, factor){
person.salary = person.salary.multiply(factor);
}
目的:代碼更清晰亲怠,減少函數(shù)的復(fù)雜度
場(chǎng)景:如果參數(shù)值影響函數(shù)內(nèi)部的控制流
例子:
function setDimension(name, value){
if(name === 'height'){}
if(name === 'width'){}
}
function setHeight(value){
this._height = value;
}
function setWidth(value){
this._width = value;
}
目的:縮短參數(shù)列表,參數(shù)配置靈活
場(chǎng)景:如果傳入多個(gè)參數(shù)傳值
例子:
const{low, high} = temperature;
if(isValidTemperature(low, high)){};
if(isValidTemperature(temperature)){};
對(duì)立:以參數(shù)取代查詢
目的:減少傳參柠辞,從而減少調(diào)用者的成本
場(chǎng)景:如果傳入多個(gè)參數(shù)团秽,并且從一個(gè)參數(shù)推導(dǎo)出另一個(gè)參數(shù)
例子:
availableVacation(employee, employee.grade);
function availableVacation(employee, grade){}
availableVacation(employee);
function availableVacation(employee){
const {grade} = employee;
}
對(duì)立:以查詢?nèi)〈鷧?shù)
目的:減少函數(shù)的副作用,以及引用關(guān)系叭首,保持函數(shù)的純凈度
場(chǎng)景:如果函數(shù)引用了一個(gè)全局變量习勤,或者引用想移除的元素
例子:
const weather ={};
targetTemperature(plan);
function targetTemperature(plan){
const{curTemperature} = weather;
}
const weather ={};
targetTemperature(plan, weather);
function targetTemperature(plan, weather){
const{curTemperature} = weather;
}
目的:防止某字段被修改
場(chǎng)景:當(dāng)不希望某個(gè)字段被修改時(shí)
例子:
class Person{
get id(){}
set id(name){}
}
class Person{
get id(){}
}
目的:增加靈活性
場(chǎng)景:不存在繼承關(guān)系時(shí)
例子:
const leadEngineer = new Employee('name','E');
const leadEngineer = createEngineer('name');
對(duì)立:以函數(shù)取代命令
目的:命令對(duì)象提供更大的靈活性,并且還可以支持撤銷焙格、生命周期的管理等附加操作
場(chǎng)景:當(dāng)普通函數(shù)無(wú)法提供強(qiáng)有力靈活性
例子:
function score(candidate, media){
let result = 0;
let healthLevel = 0;
}
function hasPassMedicalExam(){}
class Scorer{
constructor(candidate, medicalExam){
this._candidate = candidate;
this._medicalExam = medicalExam;
}
execute(){
let result = 0;
let healthLevel = 0;
}
hasPassMedicalExam(){}
}
對(duì)立:以命令取代函數(shù)
目的:函數(shù)簡(jiǎn)單化
場(chǎng)景:大多數(shù)情況下图毕,只想調(diào)用一個(gè)函數(shù),完成自己的工作眷唉,不需要函數(shù)那么復(fù)雜
例子:
class ChargeCalculator{
constructor(customer, usage){
this._customer = customer;
this._usage = usage;
}
execute(){
return this._customer.rate * this._usage;
}
}
function charge(customer, usage){
return customer.rate * usage;
}
繼承體系里上下調(diào)整:函數(shù)上移予颤、字段上移、構(gòu)造函數(shù)本體上移冬阳、函數(shù)下移蛤虐、字段下移
繼承體系添加新類或者刪除舊類:移除子類、提取超類肝陪、折疊繼承體系
一個(gè)字段僅用于類型碼使用:以子類取代類型碼
如果本來(lái)使用集成的場(chǎng)景變得不再適合:以委托取代子類驳庭、以委托取代超類
12.1 函數(shù)上移
對(duì)立:函數(shù)下移
目的:提高復(fù)用性,減少重復(fù)
場(chǎng)景:當(dāng)函數(shù)被大部分部分子類用到時(shí)
-
例子:
class Employee{} class Salesman extends Employee{ get name(){} } class Engineer extends Employee{ get name(){} }
class Employee{ get name(){} } class Salesman extends Employee{} class Engineer extends Employee{}
12.2 字段上移
12.3 構(gòu)造函數(shù)本體上移
目的:提高復(fù)用性
場(chǎng)景:當(dāng)多個(gè)子類有公共字段
-
例子:
class Party{} class Employee extends Party{ constructor(id){ this._id = id; } } class Salesman extends Employee{ constructor(id){ this._id = id; this._name = name; } }
class Party{ constructor(id){ this._id = id; } } class Employee extends Party{ constructor(id){ super(id); } } class Salesman extends Employee{ constructor(id){ super(id); this._name = name; } }
12.4 函數(shù)下移
對(duì)立:函數(shù)上移
目的:內(nèi)聚子類的方法
場(chǎng)景:當(dāng)函數(shù)被小部分子類用到時(shí)
-
例子:
class Employee{ get quota(){} } class Salesman extends Employee{} class Engineer extends Employee{}
class Employee{} class Salesman extends Employee{ get quota(){} } class Engineer extends Employee{}
12.5 字段下移
對(duì)立:字段上移
目的:內(nèi)聚子類的字段
場(chǎng)景:當(dāng)字段被小部分子類用到時(shí)
-
例子:
class Employee{ constructor(quote){ this._quote = quote; } } class Salesman extends Employee{ constructor(quote){ super(quote); } } class Engineer extends Employee{}
class Employee{} class Salesman extends Employee{ constructor(quote){ this._quote = quote; } } class Engineer extends Employee{}
12.6 以子類取代類型碼
對(duì)立:移除子類
目的:更明確地表達(dá)數(shù)據(jù)與類型之間的關(guān)系见坑,增強(qiáng)子類的擴(kuò)展性
場(chǎng)景:當(dāng)不同的狀態(tài)碼表現(xiàn)不同的行為時(shí)
-
例子:
function createEmployee(name, type){ return new Employee(name, type); }
function createEmployee(name, type){ const employeeTypes = (name) =>{ return{ 'engineer': new Engineer(name), 'salesman': new Salesman(name) } } return employeeTypes(name)[type]; }
12.7 移除子類
對(duì)立:以子類取代類型碼
目的:減少系統(tǒng)復(fù)雜度
場(chǎng)景:當(dāng)子類的用處太少時(shí)嚷掠,當(dāng)子類簡(jiǎn)單
-
例子:
class Person{ get genderCode(){ return 'X'; } } class Male extends Person{ get genderCode(){ return 'M'; } } class Female extends Person { get genderCode(){ return 'F'; } }
class Person{ constructor(genderCode){ this._genderCode = genderCode; } get genderCode(){ return this._genderCode; } }
12.8 提取超類
目的:把重復(fù)的行為收攏起來(lái)
場(chǎng)景:當(dāng)多個(gè)子類的方法基本一致時(shí)
-
例子:
class Department{ get totalAnnualCost(){} get name(){} } class Employee{ get annualCost(){} get name(){} get id(){} }
class Party{ get totalAnnualCost(){} get name(){} } class Department extends Party{ get primaryCost(){} } class Employee extends Party{ get id(){} }
12.9 折疊繼承體系
目的:合并子類捏检,減少不必要的子類荞驴,降低系統(tǒng)復(fù)雜度
場(chǎng)景:當(dāng)超類與子類沒(méi)有多大差別
-
例子:
class Employee{} class Sales extends Employee{}
class Employee{}
12.10 以委托取代子類
目的:增強(qiáng)類的擴(kuò)展性
場(chǎng)景:當(dāng)子類可能存在多種類型上的變化
-
例子
class Booking{ constructor(show, date){ this._show = show; this._date = date; } } class PremiumBooking extends Booking{ constructor(show, date, extras){ super(show, date); this._extras = extras; } }
class Booking{ constructor(show, date){ this._show = show; this._date = date; this._premium = null; } bePremium(extras){ this._premium = new PremiumBookingDelegate(this, extras); } } class PremiumBookingDelegate{ constructor(root, extras){ this._root = root; this._extras = extras; } }
12.11 以委托取代超類
目的:增強(qiáng)類的擴(kuò)展性
場(chǎng)景:當(dāng)超類的部分方法不適用于子類,不清晰的繼承關(guān)系
-
例子:
class List{} class Stack extends List{}
class List{} class Stack{ constructor(){ this._list = new List(); } }
超類的所有方法都適用于子類贯城,子類的所有實(shí)例都是超類的實(shí)例 => 使用繼承
如果發(fā)現(xiàn)繼承有問(wèn)題(擴(kuò)展困難)熊楼,再使用以委托取代超類
轉(zhuǎn)自好友先偉大佬 https://github.com/xianweics/refator-code