代碼重構(gòu)

為什么要重構(gòu)

  1. 重構(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)軟件

  1. 重構(gòu)使軟件更容易理解

編程的核心: 準(zhǔn)確說(shuō)出我想要干什么,除了告訴計(jì)算機(jī),還有其他的讀者

原來(lái)一個(gè)程序員要花一周時(shí)間來(lái)修改某段代碼,在重構(gòu)后更容易理解,現(xiàn)在只用花一小時(shí)就能搞定,這個(gè)就是時(shí)間成本,人力成本,軟件成本,公司成本的體現(xiàn)

  1. 重構(gòu)幫助找到bug

我不是一個(gè)特別好的程序員,我只是一個(gè)有著一些特別好的習(xí)慣的還不錯(cuò)的程序員

特別好的程序員可以盯著一大段代碼可以找出bug, 我不行,但是重構(gòu)了后,代碼有了結(jié)構(gòu),脈絡(luò),bug會(huì)自動(dòng)跑出來(lái)

  1. 重構(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í)間的關(guān)系

何時(shí)重構(gòu)

三次法則: 第一次去做某件事盡管去做,第二次做類似的事會(huì)有點(diǎn)反感,但是無(wú)論如何也要去做,第三次再做類似的事,你就該重構(gòu)了。

  1. 預(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)

  1. 幫助理解的重構(gòu): 使代碼更易讀懂

要把腦子里的理解轉(zhuǎn)移到代碼本身,這份知識(shí)才保存的更久,同事也能看到

給一兩個(gè)變量改名,讓他們更清晰的表達(dá)意圖
一個(gè)長(zhǎng)函數(shù)拆開幾個(gè)小函數(shù),更易理解
已經(jīng)理解了代碼意圖,但是邏輯過(guò)于迂回復(fù)雜,精簡(jiǎn)下更好

  1. 有計(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)

  2. 長(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)單多了

  3. 代碼復(fù)審的時(shí)候重構(gòu)(code review)
    很多時(shí)候自己看不出,或者經(jīng)驗(yàn)不足,重構(gòu)后仍然不夠好,那么就需要有專門的code review, 來(lái)幫助我們更好的重構(gòu)代碼

何時(shí)不該重構(gòu)

  1. 看見一堆凌亂的代碼,但是我不需要修改的時(shí)候,如果丑陋的代碼被隱藏在一個(gè)API下,就可以容忍它的丑陋,等理解工作原理后,再重構(gòu)
  2. 重寫比重構(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 {...}
最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 閱讀《重構(gòu)》的筆記獻(xiàn)上。 重構(gòu)的定義 重構(gòu)是在不改變軟件可觀察行為的前提下改善其內(nèi)部結(jié)構(gòu)。 重構(gòu)的節(jié)奏 以微小的步...
    陳宇明閱讀 11,758評(píng)論 13 64
  • 代碼重構(gòu)簡(jiǎn)介:(英語(yǔ):Code refactoring)重構(gòu)就是在不改變軟件系統(tǒng)外部行為的前提下,改善它的內(nèi)部結(jié)構(gòu)...
    TaiXiang閱讀 3,057評(píng)論 0 2
  • 一、為什么要代碼重構(gòu)(Refactoring) 在不改變系統(tǒng)功能的情況下,改變系統(tǒng)的實(shí)現(xiàn)方式。為什么要這么做?投入...
    iYeso閱讀 426評(píng)論 0 3
  • 8月19日,校長(zhǎng)以新人IG.WXZ的身份擔(dān)任IG的AD首發(fā)對(duì)陣VG。 IG這場(chǎng)比賽看下來(lái),或許算不上精彩,比賽全程...
    漁樵煮江談閱讀 535評(píng)論 0 0
  • 大學(xué)畢業(yè)后,她進(jìn)了一所國(guó)際公司的設(shè)計(jì)部,她的生活平平淡淡,早出晚歸,有次工作時(shí),她想到了他。 “他可能已經(jīng)和那個(gè)女...
    喵小絨閱讀 143評(píng)論 0 0

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