為什么要重構(gòu)
- 重構(gòu)改進(jìn)軟件的設(shè)計(jì)
設(shè)計(jì)欠佳的程序往往需要更多的代碼,重構(gòu)一個(gè)重要方向就是消除重復(fù)代碼
軟件變壞的途徑: 一個(gè)有架構(gòu)的軟件 > 修改代碼 > 沒(méi)有理解架構(gòu)設(shè)計(jì) > 代碼沒(méi)有結(jié)構(gòu) > 修改代碼 > 難以讀懂原有設(shè)計(jì) > 一個(gè)腐爛的架構(gòu)軟件
軟件變好的途徑: 一個(gè)腐爛的架構(gòu)軟件 > 修改代碼 > 改進(jìn)架構(gòu)設(shè)計(jì) > 更具有結(jié)構(gòu) > 修改代碼 > 簡(jiǎn)單易懂更易擴(kuò)展 > 一個(gè)好的架構(gòu)軟件
- 重構(gòu)使軟件更容易理解
編程的核心: 準(zhǔn)確說(shuō)出我想要干什么,除了告訴計(jì)算機(jī),還有其他的讀者
原來(lái)一個(gè)程序員要花一周時(shí)間來(lái)修改某段代碼,在重構(gòu)后更容易理解,現(xiàn)在只用花一小時(shí)就能搞定,這個(gè)就是時(shí)間成本,人力成本,軟件成本,公司成本的體現(xiàn)
- 重構(gòu)幫助找到bug
我不是一個(gè)特別好的程序員,我只是一個(gè)有著一些特別好的習(xí)慣的還不錯(cuò)的程序員
特別好的程序員可以盯著一大段代碼可以找出bug, 我不行,但是重構(gòu)了后,代碼有了結(jié)構(gòu),脈絡(luò),bug會(huì)自動(dòng)跑出來(lái)
- 重構(gòu)提高編程速度
我花在重構(gòu)上的時(shí)間,難道不是在降低開發(fā)速度嗎?
但是,經(jīng)常會(huì)聽到這樣的故事: 一開始進(jìn)展的很快,但如今想要添加一個(gè)新功能需要的時(shí)間越來(lái)越長(zhǎng),需要花很多時(shí)間想著怎么把新功能塞進(jìn)現(xiàn)有的代碼庫(kù)(最好的當(dāng)然不是塞進(jìn),是放進(jìn)), 不斷的有bug, 修復(fù)起來(lái)也越來(lái)越慢,不斷的給補(bǔ)丁打補(bǔ)丁,逐漸變成了一個(gè)考古工作者

何時(shí)重構(gòu)
三次法則: 第一次去做某件事盡管去做,第二次做類似的事會(huì)有點(diǎn)反感,但是無(wú)論如何也要去做,第三次再做類似的事,你就該重構(gòu)了。
- 預(yù)備性重構(gòu): 讓增加新功能更容易
增加新功能時(shí),對(duì)老代碼的微調(diào),會(huì)使工作容易很多
例子: 增加一個(gè)功能時(shí),發(fā)現(xiàn)有一個(gè)函數(shù)跟我功能很類似,但是里面幾個(gè)字段或者值不一樣,如果不重構(gòu),你就會(huì)把代碼復(fù)制過(guò)來(lái),修改幾個(gè)值,這就導(dǎo)致重復(fù)代碼,將來(lái)修改代碼就要改兩次,如果重構(gòu)下老的函數(shù),增加一個(gè)參數(shù),這樣就是預(yù)備性重構(gòu)
- 幫助理解的重構(gòu): 使代碼更易讀懂
要把腦子里的理解轉(zhuǎn)移到代碼本身,這份知識(shí)才保存的更久,同事也能看到
給一兩個(gè)變量改名,讓他們更清晰的表達(dá)意圖
一個(gè)長(zhǎng)函數(shù)拆開幾個(gè)小函數(shù),更易理解
已經(jīng)理解了代碼意圖,但是邏輯過(guò)于迂回復(fù)雜,精簡(jiǎn)下更好
有計(jì)劃的重構(gòu)和見機(jī)行事的重構(gòu)
上面兩個(gè)都是見機(jī)行事的重構(gòu),但是當(dāng)功能增加到一定的時(shí)候,簡(jiǎn)單的重構(gòu)會(huì)有瓶頸,會(huì)發(fā)現(xiàn)一開始考慮不周的架構(gòu)設(shè)計(jì),那么現(xiàn)在就需要有計(jì)劃的重構(gòu)長(zhǎng)期重構(gòu)
但是很多重構(gòu)會(huì)花費(fèi)幾個(gè)星期,幾個(gè)月的時(shí)間,還有一大堆混亂的依賴關(guān)系,很多人參與,不可能停下來(lái)完全重構(gòu),那么可以每個(gè)人都達(dá)成共識(shí),每天往想改進(jìn)的方向推動(dòng)一點(diǎn)點(diǎn),但是保持基本的功能不變,比如要換掉一個(gè)庫(kù),可以引入新的抽象,兼容兩個(gè)庫(kù)的接口,等調(diào)用方慢慢切換過(guò)來(lái),這樣換掉原來(lái)的庫(kù)就簡(jiǎn)單多了代碼復(fù)審的時(shí)候重構(gòu)(code review)
很多時(shí)候自己看不出,或者經(jīng)驗(yàn)不足,重構(gòu)后仍然不夠好,那么就需要有專門的code review, 來(lái)幫助我們更好的重構(gòu)代碼
何時(shí)不該重構(gòu)
- 看見一堆凌亂的代碼,但是我不需要修改的時(shí)候,如果丑陋的代碼被隱藏在一個(gè)API下,就可以容忍它的丑陋,等理解工作原理后,再重構(gòu)
- 重寫比重構(gòu)還容易的,就別重構(gòu)了
怎么重構(gòu)
1. 命名規(guī)范
好的命名是整潔代碼的核心,使用范圍越廣的越要注意命名
來(lái)看一句神秘的代碼,用一個(gè)變量表示高度,單位m
var height_rice = 4; // 高度為4米的變量, rice寫成米的英文
改變函數(shù)聲明
好辦法: 先寫一句注釋描述這個(gè)函數(shù)的作用,再把這句注釋變成函數(shù)名字
function calc(height, width) {
return height * width;
}
function calcArea(height, width) {
return height * width;
}
變量改名
var a = height * width;
var area = height * width;
2. 重復(fù)代碼
如果在一個(gè)地方以上看到相同的代碼結(jié)構(gòu),就要設(shè)法將他們合二為一, 這個(gè)時(shí)候需要提煉函數(shù)來(lái)提供統(tǒng)一的使用方式:
提煉函數(shù)
什么時(shí)候把代碼放進(jìn)獨(dú)立的函數(shù): 將意圖與實(shí)現(xiàn)分開
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
//print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
可以看到上面是想要打印日志的意圖,至于怎么打印則是實(shí)現(xiàn),所以提取函數(shù)如下,至于命名,則是秉承次函數(shù)是 "做什么" 來(lái)命名:
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
}
如果代碼是相似而不是完全相同,那么使用移動(dòng)語(yǔ)句來(lái)讓相關(guān)的代碼,結(jié)構(gòu)在一起,這是提煉函數(shù)的前提,別看這個(gè)很簡(jiǎn)單, 很多的重構(gòu)都是從這里開始
移動(dòng)語(yǔ)句
下面是一段計(jì)算商品訂單經(jīng)費(fèi)的代碼,完全沒(méi)有分類,很難理解業(yè)務(wù)流程
const pricingPlan = retrievePricingPlan();
const order = retreiveOrder();
const baseCharge = pricingPlan.base;
let charge;
const chargePerUnit = pricingPlan.unit;
const units = order.units;
let discount;
charge = baseCharge + units * chargePerUnit;
let discountableUnits = Math.max(units - pricingPlan.discountThreshold, 0);
discount = discountableUnits * pricingPlan.discountFactor;
if (order.isRepeat) discount += 20;
charge = charge - discount;
chargeOrder(charge);
采用移動(dòng)語(yǔ)句之后,把相同的功能移動(dòng)到一起分類,流程清晰,之后才能提取函數(shù)來(lái)進(jìn)一步重構(gòu)代碼
// 報(bào)價(jià)計(jì)劃
const pricingPlan = retrievePricingPlan();
const baseCharge = pricingPlan.base;
const chargePerUnit = pricingPlan.unit;
// 訂單數(shù)量
const order = retreiveOrder();
const units = order.units;
// 折扣
let discount;
let discountableUnits = Math.max(units - pricingPlan.discountThreshold, 0);
discount = discountableUnits * pricingPlan.discountFactor;
// 具體經(jīng)費(fèi)
let charge;
if (order.isRepeat) discount += 20;
charge = baseCharge + units * chargePerUnit;
charge = charge - discount;
chargeOrder(charge);
函數(shù)上移
如果重復(fù)代碼位于繼承的子類中的時(shí)候,可以把相同的代碼提到父類,避免子類之間互相調(diào)用
class Employee {...}
class Salesman extends Employee {
get name() {...}
}
class Engineer extends Employee {
get name() {...}
}
可以看到上訴子類都有相同的name()方法,可以把方法上移到父類中
class Employee {
get name() {...}
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}
3. 過(guò)長(zhǎng)的函數(shù)
老程序員的經(jīng)驗(yàn): 活的最長(zhǎng),最好的程序,其中的函數(shù)都比較短,函數(shù)越長(zhǎng),越難理解,小函數(shù)易于理解的關(guān)鍵還是在于良好的命名,好的命名就能讓人了解函數(shù)的作用,可以參考我的一個(gè)原則: 每當(dāng)感覺(jué)需要以注釋來(lái)說(shuō)明點(diǎn)什么的時(shí)候,我們就需要把說(shuō)明的東西寫進(jìn)一個(gè)獨(dú)立的函數(shù)里,并以其用途(而非實(shí)現(xiàn)手法)命名, 一定要注意函數(shù) "做什么" 和 “怎么做”之間的語(yǔ)義理解,掌握了這點(diǎn),就掌握了函數(shù)用法的精髓。
在把長(zhǎng)函數(shù)分解成小函數(shù)過(guò)程中,常常會(huì)遇到函數(shù)內(nèi)有大量的參數(shù)和臨時(shí)變量,如果你只是提取函數(shù),就會(huì)把許多參數(shù)傳遞給被提煉的函數(shù),從可讀性上面來(lái)說(shuō)沒(méi)有任何提升
以查詢?nèi)〈R時(shí)變量
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
上面生成了臨時(shí)變量basePrice, 完全可以放到類屬性里面,這樣在提取函數(shù)的時(shí)候,就少了一個(gè)臨時(shí)變量,不用當(dāng)成參數(shù)傳遞了
class Price {
get basePrice() {this._quantity * this._itemPrice;}
}
...
if (this.basePrice > 1000)
return this.basePrice * 0.95;
else
return this.basePrice * 0.98;
引入?yún)?shù)對(duì)象
對(duì)于過(guò)長(zhǎng)的參數(shù)列表,引入?yún)?shù)對(duì)象是個(gè)好辦法,這樣可以簡(jiǎn)化為一個(gè)參數(shù)結(jié)構(gòu)
function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}
function amountOverdue(startDate, endDate) {...}
上面代碼每個(gè)函數(shù)都在傳遞三個(gè)時(shí)間參數(shù),就可以提煉一個(gè)時(shí)間的數(shù)據(jù)類來(lái)統(tǒng)一管理
class DateRange {
string startDate;
string middleDate
string endDate;
}
function amountInvoiced(dateRange) {...}
function amountReceived(dateRange) {...}
function amountOverdue(dateRange) {...}
劃重點(diǎn):這項(xiàng)重構(gòu)方法具有更深層的改變 *新的數(shù)據(jù)結(jié)構(gòu) -> 重組函數(shù)來(lái)使用新結(jié)構(gòu) -> 捕捉圍繞新數(shù)據(jù)結(jié)構(gòu)的公用函數(shù) -> 構(gòu)建新的類來(lái)組合新的數(shù)據(jù)結(jié)構(gòu)和函數(shù) -> 形成新的抽象概念 -> 改變整個(gè)軟件架構(gòu)圖景, 所以說(shuō),新結(jié)構(gòu)的一小步才會(huì)有軟件架構(gòu)的一大步
函數(shù)組合成類
當(dāng)分成獨(dú)立的函數(shù)之后,這不是代碼的終點(diǎn),如果發(fā)現(xiàn)一組函數(shù)形影不離的操作著同一塊數(shù)據(jù)(做為參數(shù)傳給函數(shù)),此時(shí)就是時(shí)候組建一個(gè)類了
例如上面引入?yún)?shù)對(duì)象后的函數(shù)和數(shù)據(jù)結(jié)構(gòu)組合如下
class Amount { // 金額類
DateRange dateRange; // 時(shí)間范圍字段
Invoiced() {...}; // 發(fā)票金額方法
received() {...}; // 收支金額方法
overdue() {...}; // 欠款金額方法
}
使用類的好處:當(dāng)修改上面Amount類的dateRange這類核心數(shù)據(jù)時(shí),依賴于此的數(shù)據(jù),比如發(fā)票,收支,欠款等會(huì)與核心數(shù)據(jù)保持一致
4. 簡(jiǎn)化條件邏輯
分解條件表達(dá)式
復(fù)雜的條件邏輯是最常導(dǎo)致復(fù)雜度上升的地方之一,所以適當(dāng)?shù)姆纸馑麄兛梢愿宄谋砻髅總€(gè)分支的作用
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
charge = quantity * plan.summerRate;
else
charge = quantity * plan.regularRate + plan.regularServiceCharge;
上面代碼很難直觀看出此條件是什么作用,把這些條件和實(shí)現(xiàn)提取為函數(shù)后就非常清晰了,夏天時(shí)候的支出和其他季節(jié)的支出不同
if (summer())
charge = summerCharge();
else
charge = regularCharge();
合并條件表達(dá)式
有時(shí)候發(fā)現(xiàn)一串條件檢查:檢查條件各不相同,最終行為卻一致,這種情況可以使用‘邏輯或‘ 或‘邏輯與’合并為一個(gè)條件表達(dá)式
if (anEmployee.seniority < 2) return 0;
if (anEmployee.monthsDisabled > 12) return 0;
if (anEmployee.isPartTime) return 0;
上面都是返回0的情況,就可以提煉為一個(gè)函數(shù)統(tǒng)一返回
if (isNotEligibleForDisability()) return 0;
function isNotEligibleForDisability() {
return ((anEmployee.seniority < 2)
|| (anEmployee.monthsDisabled > 12)
|| (anEmployee.isPartTime));
}
簡(jiǎn)化嵌套條件表達(dá)式
條件表達(dá)式通常有兩種風(fēng)格,第一種:兩個(gè)條件分支都屬于正常行為,這個(gè)時(shí)候可以用 if...else...的條件表達(dá)式;第二種: 只有一個(gè)條件分支是正常行為,另一個(gè)則是異常行為,發(fā)生情況很罕見,此時(shí)應(yīng)該單獨(dú)檢查該條件,改條件為真時(shí)立即返回
function getPayAmount() {
let result;
if (isDead)
result = deadAmount();
else {
if (isSeparated)
result = separatedAmount();
else {
if (isRetired)
result = retiredAmount();
else
result = normalPayAmount();
}
}
return result;
}
上面代碼是一段根據(jù)不同員工狀態(tài)發(fā)工資的邏輯,死了有撫恤金,辭退的有補(bǔ)償金,退休了有退休金,平常就正常發(fā)工資,很顯然,正常發(fā)工資是大概率事件,其他的都可以簡(jiǎn)化為獨(dú)立判斷語(yǔ)句然后返回,這樣代碼清晰
function getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
}
以多態(tài)取代條件表達(dá)式
復(fù)雜的條件邏輯是編程中最難理解的東西,多態(tài)是面向?qū)ο缶幊痰年P(guān)鍵特征之一,大部分簡(jiǎn)單的條件判斷用if...else..或者switch...case...無(wú)關(guān)緊要,但是如果有四五個(gè)或更多的復(fù)雜條件邏輯,多態(tài)是改善這種情況的有力工具
function plumage(bird) {
switch (bird.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (bird.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (bird.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
}
把具體的實(shí)現(xiàn)封裝到類里方法,你可能會(huì)問(wèn),這不是還有switch和case嗎?注意上面只是一個(gè)獲取羽毛的方法里用了swtich和case,如果以后我們又要根據(jù)鳥的種類獲取鳥的大小,壽命等情況呢,又要在很多方法里用這些討厭的swtich..case, 但是把他們用多態(tài)抽象為類后,可以像下面使用類似構(gòu)造工廠的方式來(lái)創(chuàng)建不同品種的鳥,他們的接口都相同,后面只管調(diào)用了,往深處說(shuō),可以繼續(xù)用抽象工廠,或者控制反轉(zhuǎn)(IOC)等特性(VanGo平臺(tái)底層實(shí)現(xiàn)的精髓 ' . ' )徹底干掉這些swtich...case來(lái)實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象,當(dāng)然這些深入的東西這里就不討論了。
function createBird(bird) {
switch (bird.type) {
case 'EuropeanSwallow':
return new EuropeanSwallow(bird);
case 'AfricanSwallow':
return new AfricanSwallow(bird);
case 'NorweigianBlueParrot':
return new NorwegianBlueParrot(bird);
default:
return new Bird(bird);
}
}
class EuropeanSwallow {
get plumage() {
return "average";
}
class AfricanSwallow {
get plumage() {
return (this.numberOfCoconuts > 2) ? "tired" : "average";
}
class NorwegianBlueParrot {
get plumage() {
return (this.voltage > 100) ? "scorched" : "beautiful";
}
5. 可變數(shù)據(jù)
對(duì)數(shù)據(jù)的經(jīng)常修改是導(dǎo)致出乎意料的結(jié)果和難以發(fā)現(xiàn)的bug, 我在一處更新了數(shù)據(jù),沒(méi)有意識(shí)到另一處用期望著完全不同的數(shù)據(jù),我們要約束數(shù)據(jù)更新
封裝變量
一個(gè)好的習(xí)慣: 對(duì)于所有可變數(shù)據(jù),只要它的作用域超出了單個(gè)函數(shù),我就會(huì)將其封裝起來(lái),只允許通過(guò)函數(shù)訪問(wèn),數(shù)據(jù)的作用域越大,封裝就越重要
let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
每次獲取或者設(shè)置值的時(shí)候通過(guò)函數(shù),可以監(jiān)控或者統(tǒng)一修改內(nèi)部來(lái)改變真正的值,避免了很多bug
let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"};
export function defaultOwner() {return defaultOwnerData;}
export function setDefaultOwner(arg) {defaultOwnerData = arg;}
拆分變量
變量有各種不同的用途,要避免臨時(shí)變量被多次賦值,如果變量承擔(dān)多個(gè)責(zé)任,就應(yīng)該被分解為多個(gè)有獨(dú)立意義的變量
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
將查詢函數(shù)和修改函數(shù)分離
如果函數(shù)只提供一個(gè)值,沒(méi)有任何看得到的副作用,證明是個(gè)好函數(shù),一個(gè)好的規(guī)則是: 任何有返回值的函數(shù),都不應(yīng)該有看的見的副作用,如果遇到一個(gè) “既有返回值又有副作用” 的函數(shù),證明這里會(huì)有“看不見的”可變數(shù)據(jù),就要試著將他們分離
function getTotalOutstandingAndSendBill() {
const result = customer.invoices.reduce((total, each) => each.amount + total, 0);
sendBill();
return result;
}
可以看到在上面get函數(shù)里,sendBill()和這個(gè)函數(shù)沒(méi)有任何關(guān)系,這就是副作用,此時(shí)就要將它分離出來(lái),這是要保證函數(shù)的純凈,所謂的“純函數(shù)”,只有職責(zé)分離,才能干大事
function totalOutstanding() {
return customer.invoices.reduce((total, each) => each.amount + total, 0);
}
function sendBill() {
emailGateway.send(formatBill(customer));
}
6. 繼承關(guān)系
子類父類功能隔離
比如一些子類公用函數(shù),字段就要函數(shù)上移或者字段上移到父類來(lái)統(tǒng)一管理,相反如果是子類特有的函數(shù),字段,就要用函數(shù)下移或者字段下移到子類分別實(shí)現(xiàn),這里就不寫具體例子了,希望讀者可以自行領(lǐng)會(huì)
提煉超類
一般的面向?qū)ο蟮乃枷胧牵?code>繼承必須是真實(shí)的分類對(duì)象模型的繼承,比如鴨子繼承動(dòng)物;但是更實(shí)用的方法是: 發(fā)現(xiàn)一些共同的元素,就把他們抽取到一起,于是有了繼承關(guān)系.
class Department {
get totalAnnualCost() {...}
get name() {...}
get headCount() {...}
}
class Employee {
get annualCost() {...}
get name() {...}
get id() {...}
}
上面部門和職員都有名字和年成本這兩個(gè)屬性,那么我們把他們提到一個(gè)超類中,名叫組織,也有名字,和年成本,這樣子類部門和職員可以通過(guò)覆蓋實(shí)現(xiàn)自己的年成本計(jì)算,同時(shí)他們公司名字可能相同的,就復(fù)用父類代碼
class Party {
get name() {...}
get annualCost() {...}
}
class Department extends Party {
get annualCost() {...}
get headCount() {...}
}
class Employee extends Party {
get annualCost() {...}
get id() {...}
}
以委托取代子類
繼承是根據(jù)分類用于把屬于某一類公共的數(shù)據(jù)和行為放到超類中,每個(gè)子類根據(jù)需求覆寫部分屬性,這是繼承的本質(zhì),但是由于這種本質(zhì)體系,體現(xiàn)了他的缺點(diǎn):繼承只能處理一個(gè)分類方向上面的變化,但是子類上導(dǎo)致行為不同的原因有很多種, 比如人我根據(jù)'年齡'來(lái)繼承分類,分為‘年輕人’和'老人',但是對(duì)于'富人'和'窮人'這個(gè)分類來(lái)看,其實(shí)相同年齡的'年輕人'行為是很不同的,你們說(shuō)是吧
class Order {
get daysToShip() {
return this._warehouse.daysToShip;
}
}
class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip;
}
}
把繼承的寫法,提到超類的委托里面,這樣就是組合,所謂“對(duì)象組合優(yōu)于類繼承”也是這個(gè)道理,一個(gè)原則是,先用繼承解決代碼復(fù)用問(wèn)題,發(fā)現(xiàn)分類不對(duì)了,再改為委托
class Order {
get daysToShip() {
return (this._priorityDelegate)
? this._priorityDelegate.daysToShip
: this._warehouse.daysToShip;
}
}
class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip
}
}
以委托取代超類
如果超類的一些函數(shù)對(duì)于子類并不適合,就說(shuō)明我們不應(yīng)該通過(guò)繼承來(lái)獲得超類的功能,而改為委托,合理的繼承關(guān)系有一個(gè)重要特征: 子類的所有實(shí)例都應(yīng)該是超類的實(shí)例,通過(guò)超類的接口來(lái)使用子類的實(shí)例應(yīng)該完全不出問(wèn)題
class List {...}
class Stack extends List {...}
比如我們實(shí)現(xiàn)的棧類(Stack)原本繼承了列表類(List),但是發(fā)現(xiàn)很多列表的方法不適合棧,那就改用委托(組合)關(guān)系來(lái)把列表當(dāng)成一個(gè)屬性放在子類中,然后封裝需要用到列表類的方法即可
class Stack {
constructor() {
this._storage = new List();
}
}
class List {...}