《重構(gòu)》- 在對象之間搬移特性

一. Move Method(搬移函數(shù))

介紹

  1. 場景
    你的程序中惠奸,有個函數(shù)與其所駐類之外的另一個類進行更多交流:調(diào)用后者勺拣,或被后者調(diào)用孕似。
  2. 手法
    在該函數(shù)最常引用的類中建立一個有著類似行為的新函數(shù)送朱。將舊函數(shù)變成一個單純的委托函數(shù)搞坝,或是將舊函數(shù)完全移除搔谴。

動機

  1. “搬移函數(shù)”是重構(gòu)理論的支柱,可以使系統(tǒng)中的類更簡單桩撮。
  2. 如果一個類有太多行為敦第,或如果一個類與另一個類有太多合作而形成高度耦合,我就會搬移函數(shù)店量。

范例

重構(gòu)前

class Account {
  overdraftCharge() {
    if(this._type.isPremium()) {
      let result = 10
      if(this._daysOverdrawn > 7) {
        result += (this._daysOverdrawn -7) * 0.85
      }
      return result
    } else {
      return this._daysOverdrawn * 1.75
    }
  }

  bankCharge() {
    const result = 4.5 
    if(this._daysOverdrawn > 0) {
      result += this.overdraftCharge()
    }
    return result
  }
}

重構(gòu)后

class Account {
  bankCharge() {
    const result = 4.5 
    if(this._daysOverdrawn > 0) {
      result += this._type.overdraftCharge(this._daysOverdrawn)
    }
    return result
  }
}

class AccountType {
  overdraftCharge(daysOverdrawn) {
    if(this.isPremium()) {
      let result = 10
      if(daysOverdrawn > 7) {
        result += (daysOverdrawn -7) * 0.85
      }
      return result
    } else {
      return daysOverdrawn * 1.75
    }
  }
}

如果被搬移函數(shù)調(diào)用了Account中的另一個函數(shù)申尼,我就不能簡單地處理。這種情況下必須將源對象傳遞給目標函數(shù)垫桂。

class AccountType {
  overdraftCharge(account) {
    if(this.isPremium()) {
      let result = 10
      if(account.getDaysOverdrawn() > 7) {
        result += (account.getDaysOverdrawn() -7) * 0.85
      }
      return result
    } else {
      return account.getDaysOverdrawn() * 1.75
    }
  }
}

二. Move Field(搬移字段)

介紹

  1. 場景
    在你的程序中师幕,某個字段被其所駐類之外的另一個類更多地用到。
  2. 手法
    在目標類新建一個字段诬滩,修改源字段的所有用戶霹粥,令他們改用新字段。

動機

  1. 在類之間移動狀態(tài)和行為疼鸟,是重構(gòu)過程中必不可少的措施后控。
  2. 使用Extract Class(提煉類)時,我也可能需要搬移字段空镜。此時我會先搬移字段浩淘,然后再搬移函數(shù)捌朴。

范例

  1. 搬移只有一個函數(shù)使用的字段

重構(gòu)前

class Account {
  _type: AccountType;
  _interestRate: number;

  interestForAmount_days(amount, days) {
    return this._interestRate * amount * days / 365
  }
}

重構(gòu)后

class Account {
  _type: AccountType;

  interestForAmount_days(amount, days) {
    return this._type.getInterestRate() * amount * days / 365
  }
}

class AccountType {
  _interestRate: number;

  setInterestRate(arg) {
    this._interestRate = arg
  }

  getInterestRate() {
    return this._interestRate
  }
}
  1. 搬移有多個函數(shù)使用的字段

重構(gòu)前

class Account {
  _interestRate: number;
  _type: AccountType;

  interestForAmount_days(amount, days) {
    return this.getInterestRate() * amount * days / 365
  }

  getInterestRate() {
    return this._interestRate
  }

  setInterestRate(arg) {
    this._interestRate = arg
  }
}

重構(gòu)后

class Account {
  _type: AccountType;

  interestForAmount_days(amount, days) {
    return this.getInterestRate() * amount * days / 365
  }

  getInterestRate() {
    return this._type.getInterestRate()
  }

  setInterestRate(arg) {
    this._type.setInterestRate(arg)
  }
}

class AccountType {
  _interestRate: number;
  
  setInterestRate(arg) {
    this._interestRate = arg
  }

  getInterestRate() {
    return this._interestRate
  }
}

三. Extract Class(提煉類)

介紹

  1. 場景
    某個類做了應該由兩個類做的事。
  2. 手法
    建立一個新類张抄,將相關的字段和函數(shù)從舊類搬移到新類砂蔽。

動機

  1. 一個類應該是一個清楚的抽象,處理一些明確的責任署惯。
  2. 給類添加一項新責任時左驾,你會覺得不值得為這項責任分離出一個單獨的類。隨著責任不斷增加极谊,這個類會變得過分復雜诡右,成為一團亂麻。
  3. 如果某些數(shù)據(jù)和某些函數(shù)總是一起出現(xiàn)轻猖,某些數(shù)據(jù)經(jīng)常同時變化甚至彼此相依帆吻,這就表示你應該將他們分離出去。

范例

重構(gòu)前

class Person{
  _name: string;
  _officeAreaCode: string;
  _officeNumber: string;

  getName() {
    return this._name
  }

  getTelephoneNumber() {
    return `(${this._officeAreaCode})${this._officeNumber}`
  }

  getOfficeAreaCode() {
    return this._officeAreaCode
  }

  setOfficeAreaCode(arg) {
    this._officeAreaCode = arg
  }

  getOfficeNumber() {
    return this._officeNumber
  }

  setOfficeNumber(arg) {
    this._officeNumber = arg
  }
}

重構(gòu)后

class Person {
  _name: string;
  _officeTelephone = new TelephoneNumber()

  getName() {
    return this._name
  }

  getTelephoneNumber() {
    return this._officeTelephone.getTelephoneNumber()
  }

  getOfficeTelephone() {
    return this._officeTelephone
  }
}

class TelephoneNumber {
  _areaCode: string;
  _number: string;

  getTelephoneNumber() {
    return `(${this._areaCode})${this._number}`
  }

  getAreaCode() {
    return this._areaCode
  }

  setAreaCode(arg) {
    this._areaCode = arg
  }

  getNumber() {
    return this._number
  }

  setNumber(arg) {
    this._number = arg
  }
}

四. Inline Class(將類內(nèi)聯(lián)化)

介紹

  1. 場景
    某個類沒有做太多事情咙边。
  2. 手法
    將這個類的所有特性搬移到另一個類中猜煮,然后移除原類。

動機

  1. Inline Class(將類內(nèi)聯(lián)化)正好與Extract Class(提煉類)相反样眠。
  2. 如果一個類不再承擔足夠責任友瘤、不再有單獨存在的理由,我們就會挑選這一“萎縮類”的最頻繁用戶(也是個類)檐束,以Inline Class手法將“萎縮類”塞進另一個類中辫秧。

范例

重構(gòu)前

class Person {
  _name: string;
  _officeTelephone = new TelephoneNumber()

  getName() {
    return this._name
  }

  getTelephoneNumber() {
    return this._officeTelephone.getTelephoneNumber()
  }

  getOfficeTelephone() {
    return this._officeTelephone
  }
}

class TelephoneNumber {
  _areaCode: string;
  _number: string;

  getTelephoneNumber() {
    return `(${this._areaCode})${this._number}`
  }

  getAreaCode() {
    return this._areaCode
  }

  setAreaCode(arg) {
    this._areaCode = arg
  }

  getNumber() {
    return this._number
  }

  setNumber(arg) {
    this._number = arg
  }
}

重構(gòu)后

class Person{
  _name: string;
  _officeAreaCode: string;
  _officeNumber: string;

  getName() {
    return this._name
  }

  getTelephoneNumber() {
    return `(${this._officeAreaCode})${this._officeNumber}`
  }

  getOfficeAreaCode() {
    return this._officeAreaCode
  }

  setOfficeAreaCode(arg) {
    this._officeAreaCode = arg
  }

  getOfficeNumber() {
    return this._officeNumber
  }

  setOfficeNumber(arg) {
    this._officeNumber = arg
  }
}

五. Hide Delegate(隱藏“委托關系”)

介紹

  1. 場景
    客戶通過一個委托類來調(diào)用另一個對象。
  2. 手法
    在服務類上建立客戶所需的所有函數(shù)被丧,用以隱藏委托關系盟戏。

動機

  1. “封裝”即使不是對象的最關鍵特征,也是最關鍵特征之一甥桂。
  2. “封裝”意味每個對象都應該盡可能少了解系統(tǒng)的其他部分柿究,一旦發(fā)生變化,需要了解這一變化的對象就會比較少黄选。
  3. 隱藏“委托關系”蝇摸,當委托關系發(fā)生變化時,變化也將被限制在服務對象中办陷,不會波及客戶貌夕。
  4. 一旦你對所有客戶都隱藏了委托關系,就不再需要在服務對象的接口中公開被委托對象了民镜。

范例

重構(gòu)前

class Person {
  _department: Department;

  getDepartment() {
    return this._department
  }

  setDepartment(arg) {
    this._department = arg
  }
}

class Department {
  _chargeCode: string;
  _manager: Person;

  Department(manager) {
    this._manager = manager
  }

  getManager() {
    return this._manager
  }
}

const m = john.getDepartment().getManager()

重構(gòu)后

class Person {
  _department: Department;

  getManager() {
    return this._department.getManager()
  }

  setDepartment(arg) {
    this._department = arg
  }
}

const m = john.getManager()

六. Remove Middle Man(移除中間人)

介紹

  1. 場景
    某個類做了過多的簡單委托動作啡专。
  2. 手法
    讓客戶直接調(diào)用受托類。

動機

  1. “封裝受委托對象”的代價就是:每當客戶要使用受托類的新特性時制圈,必須在服務類添加一個簡單委托函數(shù)们童。
  2. 隨著受托類特性越來越復雜畔况,委托函數(shù)越來越多,服務類完全成了一個“中間人”慧库,此時你就應該讓客戶直接調(diào)用受托類跷跪。
  3. 重構(gòu)的意義就在于:你永遠不必說對不起——只要把出問題的地方修補好就行了。

范例

重構(gòu)前

class Person {
  _department: Department;

  getManager() {
    return this._department.getManager()
  }

  setDepartment(arg) {
    this._department = arg
  }
}

class Department {
  _chargeCode: string;
  _manager: Person;

  Department(manager) {
    this._manager = manager
  }

  getManager() {
    return this._manager
  }
}

const m = john.getManager()

重構(gòu)后

class Person {
  _department: Department;

  getDepartment() {
    return this._department
  }

  setDepartment(arg) {
    this._department = arg
  }
}

const m = john.getDepartment().getManager()

七. Introduce Foreign Method(引入外加函數(shù))

介紹

  1. 場景
    你需要為提供服務的類增加一個函數(shù)完沪,但你無法修改這個類域庇。
  2. 手法
    在客戶類中建立一個函數(shù)嵌戈,并以第一參數(shù)形式傳入一個服務類實例覆积。

范例

重構(gòu)前

//創(chuàng)建一個日期的下一天
const newStart = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)

重構(gòu)后

const nextDay = (date) => {
  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)
}

const newStart = nextDay(date)

八. Introduce Local Extension(引入本地擴展)

介紹

  1. 場景
    你需要為服務類提供一些額外函數(shù),但你無法修改這個類熟呛。
  2. 手法
    建立一個新類宽档,使它包含這些額外函數(shù)。讓這個擴展品成為源類的子類或包裝類庵朝。

動機

  1. 你需要為提供服務的類增加多個函數(shù)吗冤,但你無法修改這個類。
  2. 你需要將這些函數(shù)組織在一起九府,放到一個合適的地方去椎瘟。子類化(subclassing)和包裝(wrapping)是兩種常用的本地擴展。
  3. 本地擴展是一個獨立的類侄旬,但也是被擴展類的子類型:它提供源類的一切特性肺蔚,同時額外添加新特性。

范例

使用子類

class MfDateSub extends Date{
  nextDay() {
    return new Date(this.getFullYear(), this.getMonth(), this.getDate() + 1)
  }
}

const mySubDate = new MfDateSub(2018, 9, 10)
console.log(mySubDate.nextDay())

注釋:該代碼只是為了演示使用子類擴展方式的原理儡羔,運行會報錯宣羊。
使用包裝類

class MfDateWrap {
  constructor() {
    this._original = new Date(...arguments)
  }

  getFullYear() {
    return this._original.getFullYear()
  }

  getMonth() {
    return this._original.getMonth()
  }

  getDate() {
    return this._original.getDate()
  }

  nextDay() {
    return new Date(this.getFullYear(), this.getMonth(), this.getDate() + 1)
  }
}

const mfDateWrap = new MfDateWrap(2018, 9, 10)
console.log(mfDateWrap.nextDay())

注釋:使用包裝類時需要為原始類(Date)的所有函數(shù)提供委托函數(shù),這里只展示了三個函數(shù)汰蜘,其他函數(shù)的處理依此類推仇冯。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市族操,隨后出現(xiàn)的幾起案子苛坚,更是在濱河造成了極大的恐慌,老刑警劉巖色难,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泼舱,死亡現(xiàn)場離奇詭異,居然都是意外死亡莱预,警方通過查閱死者的電腦和手機柠掂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來依沮,“玉大人涯贞,你說我怎么就攤上這事枪狂。” “怎么了宋渔?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵州疾,是天一觀的道長。 經(jīng)常有香客問我皇拣,道長严蓖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任氧急,我火速辦了婚禮颗胡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吩坝。我一直安慰自己毒姨,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布钉寝。 她就那樣靜靜地躺著弧呐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嵌纲。 梳的紋絲不亂的頭發(fā)上俘枫,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音逮走,去河邊找鬼鸠蚪。 笑死,一個胖子當著我的面吹牛言沐,可吹牛的內(nèi)容都是我干的邓嘹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼险胰,長吁一口氣:“原來是場噩夢啊……” “哼汹押!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起起便,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棚贾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后榆综,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妙痹,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年鼻疮,在試婚紗的時候發(fā)現(xiàn)自己被綠了怯伊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡判沟,死狀恐怖耿芹,靈堂內(nèi)的尸體忽然破棺而出崭篡,到底是詐尸還是另有隱情,我是刑警寧澤吧秕,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布琉闪,位于F島的核電站,受9級特大地震影響砸彬,放射性物質(zhì)發(fā)生泄漏颠毙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一砂碉、第九天 我趴在偏房一處隱蔽的房頂上張望蛀蜜。 院中可真熱鬧,春花似錦绽淘、人聲如沸涵防。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偏瓤,卻和暖如春杀怠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厅克。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工赔退, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人证舟。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓硕旗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親女责。 傳聞我的和親對象是個殘疾皇子漆枚,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容