一. Full Up Field(字段上移)
介紹
- 場(chǎng)景
兩個(gè)子類擁有相同的字段诬垂。 - 手法
將該字段移至超類回季。
動(dòng)機(jī)
本項(xiàng)重構(gòu)去除了重復(fù)的數(shù)據(jù)聲明互纯,并且將使用該字段的行為從子類移至超類壮吩,去除重復(fù)的行為檐蚜。
二. Pull Up Method(函數(shù)上移)
介紹
- 場(chǎng)景
有些函數(shù)蟆技,在各個(gè)子類中產(chǎn)生完全相同的結(jié)果玩敏。 - 手法
將該函數(shù)移至超類。
動(dòng)機(jī)
- 避免行為重復(fù)是很重要的质礼。重復(fù)自身只會(huì)成為錯(cuò)誤的滋生地聊品,此外別無價(jià)值。
- 無論何時(shí)几苍,只要系統(tǒng)之內(nèi)出現(xiàn)重復(fù)翻屈,你就面臨“修改其中一個(gè)卻未能修改另一個(gè)”的風(fēng)險(xiǎn)。
三. Pull Up Constructor Body(構(gòu)造函數(shù)本體上移)
介紹
- 場(chǎng)景
你在各個(gè)子類中擁有一些構(gòu)造函數(shù)妻坝,它們的本體幾乎完全一致伸眶。 - 手法
在超類中新建一個(gè)構(gòu)造函數(shù),并在子類構(gòu)造函數(shù)中調(diào)用它刽宪。
動(dòng)機(jī)
如果你看見各個(gè)子類中的函數(shù)有共同行為厘贼,第一個(gè)念頭應(yīng)該是將共同行為提煉到一個(gè)獨(dú)立函數(shù)中,然后將這個(gè)函數(shù)提升到超類圣拄。
范例
重構(gòu)前
class Employee {
_name;
_id;
constructor(name, id) {
this._name = name
this._id = id
}
isPriviliged() {}
assignCar() {}
}
class Manager extends Employee {
constructor(name, id, grade) {
this._name = name
this._id = id
this._grade = grade
if(this.isPriviliged()) {
this.assignCar()
}
}
}
重構(gòu)后
class Employee {
_name;
_id;
constructor(name, id) {
this._name = name
this._id = id
}
initialize() {
if(this.isPriviliged()) {
this.assignCar()
}
}
isPriviliged() {}
assignCar() {}
}
class Manager extends Employee {
constructor(name, id, grade) {
super(name, id)
this._grade = grade
this.initialize()
}
}
四. Push Down Method(函數(shù)下移)
介紹
- 場(chǎng)景
超類中的某個(gè)函數(shù)只與部分(而非全部)子類有關(guān)嘴秸。 - 手法
將這個(gè)函數(shù)移到相關(guān)的那些子類去。
動(dòng)機(jī)
Push Down Method
(函數(shù)下移)與Pull Up Method
(函數(shù)上移)恰恰相反庇谆。
五. Push Down Field(字段下移)
介紹
- 場(chǎng)景
超類中的某個(gè)字段只被部分(而非全部)子類用到岳掐。 - 手法
將這個(gè)字段移到需要它的那些子類去。
動(dòng)機(jī)
Push Down Field
(字段下移)與Full Up Field
(字段上移)恰恰相反饭耳。
六. Extract Subclass(提煉子類)
介紹
- 場(chǎng)景
類中的某些特性只被某些(而非全部)實(shí)例用到串述。 - 手法
新建一個(gè)子類,將上面所說的那一部分特性移到子類中寞肖。
范例
重構(gòu)前
class JobItem{
constructor(unitPrice, quantity, isLabor, employee) {
this._unitPrice = unitPrice
this._quantity = quantity
this._isLabor = isLabor
this._employee = employee
}
getUnitPrice() {
return this._isLabor ? this._employee.getRate() : this._unitPrice
}
getTotalPrice() {
return this.getUnitPrice() * this._quantity
}
getQuantity() {
return this._quantity
}
getEmployee() {
return this._employee
}
}
class Employee {
constructor(rate) {
this._rate = rate
}
getRate() {
return this._rate
}
}
重構(gòu)后
class JobItem{
constructor(unitPrice, quantity) {
this._unitPrice = unitPrice
this._quantity = quantity
}
getUnitPrice() {
return this._unitPrice
}
getTotalPrice() {
return this.getUnitPrice() * this._quantity
}
getQuantity() {
return this._quantity
}
}
class LaborItem extends JobItem{
constructor(quantity, employee) {
super(0, quantity)
this._employee = employee
}
getUnitPrice() {
return this._employee.getRate()
}
getEmployee() {
return this._employee
}
}
class Employee {
constructor(rate) {
this._rate = rate
}
getRate() {
return this._rate
}
}
七. Extract Superclass(提煉超類)
介紹
- 場(chǎng)景
兩個(gè)類有相似特性纲酗。 - 手法
為這兩個(gè)類建立一個(gè)超類,將相同特性移至超類新蟆。
動(dòng)機(jī)
- 重復(fù)代碼是系統(tǒng)中最糟糕的東西之一觅赊。如果你在不同地方做同一件事情,一旦需要修改那些動(dòng)作琼稻,你就得平白做更多的修改吮螺。
范例
重構(gòu)前
class Employee{
constructor(name, id, annualCost) {
this._name = name
this._id = id
this._annualCost = annualCost
}
getAnnualCost() {
return this._annualCost
}
getId() {
return this._id
}
getName() {
return this._name
}
}
class Department{
_staff = []
constructor(name) {
this._name = name
}
getStaff() {
return this._staff
}
getTotalAnnualCost() {
let result = 0
this.getStaff().forEach(item => {
result += item.getAnnualCost()
})
return result
}
getHeadCount() {
return this._staff.length
}
addStaff(arg) {
return this._staff.push(arg)
}
getName() {
return this._name
}
}
重構(gòu)后
class Party{
constructor(name) {
this._name = name
}
getName() {
return this._name
}
getAnnualCost() {}
}
class Employee extends Party{
constructor(name, id, annualCost) {
super(name)
this._id = id
this._annualCost = annualCost
}
getAnnualCost() {
return this._annualCost
}
getId() {
return this._id
}
}
class Department extends Party{
_staff = []
constructor(name) {
super(name)
}
getStaff() {
return this._staff
}
getAnnualCost() {
let result = 0
this.getStaff().forEach(item => {
result += item.getAnnualCost()
})
return result
}
getHeadCount() {
return this._staff.length
}
addStaff(arg) {
return this._staff.push(arg)
}
}
八. Extract Interface(提煉接口)
介紹
- 場(chǎng)景
若干客戶使用類接口中的同一子集,或者兩個(gè)類的接口有部分相同。 - 手法
將相同的子集提煉到一個(gè)獨(dú)立接口中规脸。
范例
JavaScript
沒有接口的概念坯约,無法用代碼演示該重構(gòu)手法熊咽。
九. Collapse Hierarchy(折疊繼承體系)
介紹
- 場(chǎng)景
超類和子類之間無太大區(qū)別莫鸭。 - 手法
將它們合為一體。
動(dòng)機(jī)
- 繼承體系很容易變得過分復(fù)雜横殴。重構(gòu)繼承體系往往是將函數(shù)和字段在體系中上下移動(dòng)被因。
- 如果發(fā)現(xiàn)某個(gè)子類并未帶來該有的價(jià)值,需要把超類與子類合并起來衫仑。
十. Form Template Method(塑造模板函數(shù))
介紹
- 場(chǎng)景
你有一些子類梨与,其中相應(yīng)的某些函數(shù)以相同順序執(zhí)行類似的操作,但各個(gè)操作的細(xì)節(jié)上有所不同文狱。 - 手法
將這些操作分別放進(jìn)獨(dú)立函數(shù)中粥鞋,并保持他們都有相同的簽名,于是原函數(shù)也就變得相同了瞄崇。然后將原函數(shù)上移至超類呻粹。
動(dòng)機(jī)
- 繼承是避免重復(fù)行為的一個(gè)強(qiáng)大工具。只要你看見兩個(gè)子類之中有類似的函數(shù)苏研,就可以把他們提升到超類等浊。
- 如果子類中兩個(gè)函數(shù)以相同順序執(zhí)行大致相近的操作,但是各操作不完全相同摹蘑。這種情況下我們可以將執(zhí)行操作的序列移至超類筹燕,并借助多態(tài)保證各操作仍得以保持差異性。這樣的函數(shù)被稱為模板方法衅鹿。
范例
class Customer {
statement() {
const rentals = this._rentals
// \n表示換行
let result = `Rental Record for ${this.getName()}\n`
rentals.forEach(item => {
//\t表示制表符
result += `\t${item.getMovie().getTitle()}\t${item.getCharge()}\n`
})
result += `Amount owed is ${this.getTotalCharge()} \n`
result += `You earned ${this.getTotalFrequentRenterPoints()} frequent renter points`
return result
}
htmlStatement() {
const rentals = this._rentals
let result = `<h1>Rentals for<EM>${this.getName()}</EM></h1><P>\n`
rentals.forEach(item => {
result += `${item.getMovie().getTitle()}: ${item.getCharge()}<BR>\n`
})
result += `<P> You owe <EM> ${this.getTotalCharge()}</EM><P>\n`
result += `On this rental you earned <EM> ${this.getTotalFrequentRenterPoints()} </EM> frequent renter points <P>`
return result
}
}
重構(gòu)后
class Customer {
statement() {
return new TextStatement().value(this)
}
htmlStatement() {
return new HtmlStatement().value(this)
}
}
class Statement {
value(customer) {
const rentals = customer._rentals
let result = this.headerString(customer)
rentals.forEach(item => {
result += this.eachRentalString(item)
})
result += this.footerString(customer)
return result
}
headerString(customer) {}
eachRentalString(customer) {}
footerString(customer) {}
}
class TextStatement extends Statement {
headerString(customer) {
return `Rental Record for ${customer.getName()}\n`
}
eachRentalString(rental) {
return `\t${rental.getMovie().getTitle()}\t${rental.getCharge()}\n`
}
footerString(customer) {
return `Amount owed is ${customer.getTotalCharge()} \n You earned ${customer.getTotalFrequentRenterPoints()} frequent renter points`
}
}
class HtmlStatement extends Statement {
headerString(customer) {
return `<h1>Rentals for<EM>${customer.getName()}</EM></h1><P>\n`
}
eachRentalString(rental) {
return `${rental.getMovie().getTitle()}: ${rental.getCharge()}<BR>\n`
}
footerString(customer) {
return `<P> You owe <EM> ${customer.getTotalCharge()}</EM><P>\n On this rental you earned <EM> ${customer.getTotalFrequentRenterPoints()} </EM> frequent renter points <P>`
}
}
十一. Replace Inheritance with Delegation(以委托取代繼承)
介紹
- 場(chǎng)景
某個(gè)子類只使用超類接口中的一部分撒踪,或是根本不需要繼承而來的數(shù)據(jù)。 - 手法
在子類中新建一個(gè)字段用以保存超類大渤;調(diào)整子類函數(shù)糠涛,令它改而委托超類;然后去掉兩者之間的繼承關(guān)系兼犯。
動(dòng)機(jī)
- 繼承是個(gè)好東西忍捡,但有時(shí)候它并不是你要的。
- 如果從超類中繼承了許多用不到的方法或數(shù)據(jù)切黔,就應(yīng)該以委托取代繼承砸脊。
范例
重構(gòu)前
class MyStack extends Vector {
push(element) {
this.insertElement(element, 0)
}
pop() {
const result = this.firstElement()
this.removeElementAt(0)
return result
}
}
重構(gòu)后
class MyStack {
_vector = new Vector()
push(element) {
this._vector.insertElement(element, 0)
}
pop() {
const result = this._vector.firstElement()
this._vector.removeElementAt(0)
return result
}
size() {
return this.Vector.size()
}
isEmpty() {
return this._vector.isEmpty()
}
}
十二. Replace Delegation with Inheritance(以繼承取代委托)
介紹
- 場(chǎng)景
你在兩個(gè)類之間使用委托關(guān)系,并經(jīng)常為整個(gè)接口編寫許多極簡(jiǎn)單的委托函數(shù)纬霞。 - 手法
讓委托類繼承受委托類凌埂。
動(dòng)機(jī)
- 本重構(gòu)與
Replace Inheritance with Delegation
(以委托取代繼承)恰恰相反。 - 如果你發(fā)現(xiàn)自己需要使用受委托類中的所有函數(shù)诗芜,并且費(fèi)了很大力氣編寫所有極簡(jiǎn)的委托函數(shù)瞳抓,本重構(gòu)可以幫助你輕松回頭使用繼承埃疫。
- 如果你并沒有使用受委托類的所有函數(shù),那么就不應(yīng)該將委托關(guān)系替換為繼承關(guān)系孩哑。
- 如果受托對(duì)象被不止一個(gè)其他對(duì)象共享栓霜,并且受托對(duì)象時(shí)可變的,你就不能將委托關(guān)系替換為繼承關(guān)系横蜒。
范例
重構(gòu)前
class Employee {
_person = new Person()
getName() {
return this._person.getName()
}
setName(arg) {
this._person.setName(arg)
}
toString() {
return `Emp: ${this._person.getLastName()}`
}
}
class Person{
_name
getName() {
return this._name
}
setName(arg) {
this._name = arg
}
getLastName() {
return this._name.substring(this._name.lastIndexOf(' ') + 1)
}
}
重構(gòu)后
class Employee extends Person {
toString() {
return `Emp: ${this.getLastName()}`
}
}
class Person{
_name
getName() {
return this._name
}
setName(arg) {
this._name = arg
}
getLastName() {
return this._name.substring(this._name.lastIndexOf(' ') + 1)
}
}