一. Move Method(搬移函數(shù))
介紹
- 場景
你的程序中惠奸,有個函數(shù)與其所駐類之外的另一個類進行更多交流:調(diào)用后者勺拣,或被后者調(diào)用孕似。 - 手法
在該函數(shù)最常引用的類中建立一個有著類似行為的新函數(shù)送朱。將舊函數(shù)變成一個單純的委托函數(shù)搞坝,或是將舊函數(shù)完全移除搔谴。
動機
- “搬移函數(shù)”是重構(gòu)理論的支柱,可以使系統(tǒng)中的類更簡單桩撮。
- 如果一個類有太多行為敦第,或如果一個類與另一個類有太多合作而形成高度耦合,我就會搬移函數(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(搬移字段)
介紹
- 場景
在你的程序中师幕,某個字段被其所駐類之外的另一個類更多地用到。 - 手法
在目標類新建一個字段诬滩,修改源字段的所有用戶霹粥,令他們改用新字段。
動機
- 在類之間移動狀態(tài)和行為疼鸟,是重構(gòu)過程中必不可少的措施后控。
- 使用Extract Class(提煉類)時,我也可能需要搬移字段空镜。此時我會先搬移字段浩淘,然后再搬移函數(shù)捌朴。
范例
- 搬移只有一個函數(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
}
}
- 搬移有多個函數(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(提煉類)
介紹
- 場景
某個類做了應該由兩個類做的事。 - 手法
建立一個新類张抄,將相關的字段和函數(shù)從舊類搬移到新類砂蔽。
動機
- 一個類應該是一個清楚的抽象,處理一些明確的責任署惯。
- 給類添加一項新責任時左驾,你會覺得不值得為這項責任分離出一個單獨的類。隨著責任不斷增加极谊,這個類會變得過分復雜诡右,成為一團亂麻。
- 如果某些數(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)化)
介紹
- 場景
某個類沒有做太多事情咙边。 - 手法
將這個類的所有特性搬移到另一個類中猜煮,然后移除原類。
動機
- Inline Class(將類內(nèi)聯(lián)化)正好與Extract Class(提煉類)相反样眠。
- 如果一個類不再承擔足夠責任友瘤、不再有單獨存在的理由,我們就會挑選這一“萎縮類”的最頻繁用戶(也是個類)檐束,以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(隱藏“委托關系”)
介紹
- 場景
客戶通過一個委托類來調(diào)用另一個對象。 - 手法
在服務類上建立客戶所需的所有函數(shù)被丧,用以隱藏委托關系盟戏。
動機
- “封裝”即使不是對象的最關鍵特征,也是最關鍵特征之一甥桂。
- “封裝”意味每個對象都應該盡可能少了解系統(tǒng)的其他部分柿究,一旦發(fā)生變化,需要了解這一變化的對象就會比較少黄选。
- 隱藏“委托關系”蝇摸,當委托關系發(fā)生變化時,變化也將被限制在服務對象中办陷,不會波及客戶貌夕。
- 一旦你對所有客戶都隱藏了委托關系,就不再需要在服務對象的接口中公開被委托對象了民镜。
范例
重構(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(移除中間人)
介紹
- 場景
某個類做了過多的簡單委托動作啡专。 - 手法
讓客戶直接調(diào)用受托類。
動機
- “封裝受委托對象”的代價就是:每當客戶要使用受托類的新特性時制圈,必須在服務類添加一個簡單委托函數(shù)们童。
- 隨著受托類特性越來越復雜畔况,委托函數(shù)越來越多,服務類完全成了一個“中間人”慧库,此時你就應該讓客戶直接調(diào)用受托類跷跪。
- 重構(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ù))
介紹
- 場景
你需要為提供服務的類增加一個函數(shù)完沪,但你無法修改這個類域庇。 - 手法
在客戶類中建立一個函數(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(引入本地擴展)
介紹
- 場景
你需要為服務類提供一些額外函數(shù),但你無法修改這個類熟呛。 - 手法
建立一個新類宽档,使它包含這些額外函數(shù)。讓這個擴展品成為源類的子類或包裝類庵朝。
動機
- 你需要為提供服務的類增加多個函數(shù)吗冤,但你無法修改這個類。
- 你需要將這些函數(shù)組織在一起九府,放到一個合適的地方去椎瘟。子類化(
subclassing
)和包裝(wrapping
)是兩種常用的本地擴展。 - 本地擴展是一個獨立的類侄旬,但也是被擴展類的子類型:它提供源類的一切特性肺蔚,同時額外添加新特性。
范例
使用子類
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ù)的處理依此類推仇冯。