一. Move Method(搬移函数)
介绍
- 场景
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 - 手法
在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
动机
- “搬移函数”是重构理论的支柱,可以使系统中的类更简单。
- 如果一个类有太多行为,或如果一个类与另一个类有太多合作而形成高度耦合,我就会搬移函数。
范例
重构前
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
}
}
重构后
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
}
}
}
如果被搬移函数调用了Account
中的另一个函数,我就不能简单地处理。这种情况下必须将源对象传递给目标函数。
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(搬移字段)
介绍
- 场景
在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。 - 手法
在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段。
动机
- 在类之间移动状态和行为,是重构过程中必不可少的措施。
- 使用Extract Class(提炼类)时,我也可能需要搬移字段。此时我会先搬移字段,然后再搬移函数。
范例
- 搬移只有一个函数使用的字段
重构前
class Account {
_type: AccountType;
_interestRate: number;
interestForAmount_days(amount, days) {
return this._interestRate * amount * days / 365
}
}
重构后
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
}
}
- 搬移有多个函数使用的字段
重构前
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
}
}
重构后
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(提炼类)
介绍
- 场景
某个类做了应该由两个类做的事。 - 手法
建立一个新类,将相关的字段和函数从旧类搬移到新类。
动机
- 一个类应该是一个清楚的抽象,处理一些明确的责任。
- 给类添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的类。随着责任不断增加,这个类会变得过分复杂,成为一团乱麻。
- 如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就表示你应该将他们分离出去。
范例
重构前
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
}
}
重构后
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(将类内联化)
介绍
- 场景
某个类没有做太多事情。 - 手法
将这个类的所有特性搬移到另一个类中,然后移除原类。
动机
- Inline Class(将类内联化)正好与Extract Class(提炼类)相反。
- 如果一个类不再承担足够责任、不再有单独存在的理由,我们就会挑选这一“萎缩类”的最频繁用户(也是个类),以Inline Class手法将“萎缩类”塞进另一个类中。
范例
重构前
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
}
}
重构后
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(隐藏“委托关系”)
介绍
- 场景
客户通过一个委托类来调用另一个对象。 - 手法
在服务类上建立客户所需的所有函数,用以隐藏委托关系。
动机
- “封装”即使不是对象的最关键特征,也是最关键特征之一。
- “封装”意味每个对象都应该尽可能少了解系统的其他部分,一旦发生变化,需要了解这一变化的对象就会比较少。
- 隐藏“委托关系”,当委托关系发生变化时,变化也将被限制在服务对象中,不会波及客户。
- 一旦你对所有客户都隐藏了委托关系,就不再需要在服务对象的接口中公开被委托对象了。
范例
重构前
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()
重构后
class Person {
_department: Department;
getManager() {
return this._department.getManager()
}
setDepartment(arg) {
this._department = arg
}
}
const m = john.getManager()
六. Remove Middle Man(移除中间人)
介绍
- 场景
某个类做了过多的简单委托动作。 - 手法
让客户直接调用受托类。
动机
- “封装受委托对象”的代价就是:每当客户要使用受托类的新特性时,必须在服务类添加一个简单委托函数。
- 随着受托类特性越来越复杂,委托函数越来越多,服务类完全成了一个“中间人”,此时你就应该让客户直接调用受托类。
- 重构的意义就在于:你永远不必说对不起——只要把出问题的地方修补好就行了。
范例
重构前
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()
重构后
class Person {
_department: Department;
getDepartment() {
return this._department
}
setDepartment(arg) {
this._department = arg
}
}
const m = john.getDepartment().getManager()
七. Introduce Foreign Method(引入外加函数)
介绍
- 场景
你需要为提供服务的类增加一个函数,但你无法修改这个类。 - 手法
在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
范例
重构前
//创建一个日期的下一天
const newStart = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)
重构后
const nextDay = (date) => {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)
}
const newStart = nextDay(date)
八. Introduce Local Extension(引入本地扩展)
介绍
- 场景
你需要为服务类提供一些额外函数,但你无法修改这个类。 - 手法
建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
动机
- 你需要为提供服务的类增加多个函数,但你无法修改这个类。
- 你需要将这些函数组织在一起,放到一个合适的地方去。子类化(
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
)的所有函数提供委托函数,这里只展示了三个函数,其他函数的处理依此类推。