【重构笔记03】对象之间搬迁特性
前言
在面向对象设计过程中,“决定把责任放在那儿”即使不是最重要的事,也是最重要的事之一。
这个思想其实对类较重要,在js中,函数应该完成什么职责,也需要分清楚,不要函数做了自己的事情,又做了一部分其它函数的事情。
在js中,往往只有一个类,面向对象的思想其实比较难,没有一定功力不能很好的运用,我也在学习
很多js中其实就只有一个类,一个插件就是一个类,而且这个类还是假的,继承、封装神马的要理解还是不容易的,所以路还长!
搬迁函数
我们的程序中如果有个函数与其所驻类的另一个类有更多的交流(调用后者、或者被后者调用)
那么在该函数最常引用的类中建立一个有着类似行为的新函数,将久函数变成一个单纯的委托函数,或者移除旧函数
搬迁函数是重构理论的支柱,如果一个类有太多行为,或者一个类与另一个类有太多合作而成高度耦合,我们就需要搬迁函数
PS:其实在单页应用中,MVC的结构很可能导致这种情况发现,后面点我们搞点代码出来看看
我们的程序中会有这样的函数,使用另一个对象次数比使用自己的次数还多,搬迁时这就是函数归属的重要依据
来一个例子吧,这里利用一个表示“账户”的Account类来说明
1 var AccountType = function () {}; 2 AccountType.prototype = { 3 isPremium: function () { return true; } 4 }; 5 var Account = function () { 6 this._type = new AccountType(); 7 this._daysOverdran; 8 }; 9 Account.prototype = { 10 overdraftCharge: function () { 11 if (this._type.isPremium()) { 12 var result = 10; 13 if (this._daysOverdran > 7) result += (this._daysOverdrawn - 7) * 0.85; 14 return result; 15 } else { 16 return this._daysOverdran * 1.75; 17 } 18 }, 19 bankCharge: function () { 20 var result = 4.5; 21 if (this._daysOverdran > 0) result += this.overdraftCharge(); 22 return result; 23 } 24 };
在这个程序中,加入有几种新账户,每一种都有自己的“透支金额计费规则”,所以overdraftCharge可能需要搬迁到AccountType中去
如何做
首先,我们要观察overdraftCharge使用的每一项特性,考虑那些特性是否可与他一起搬迁
这个程序中,_dayOverdrawn会被留下,因为这个值会随不同账号变化而变化,于是调整后的代码如下:
1 var AccountType = function () {}; 2 AccountType.prototype = { 3 isPremium: function () { return true; }, 4 overdraftCharge: function (account) { 5 if (this.isPremium()) { 6 var result = 10; 7 if (account.getDaysOverdran() > 7) result += (account.getDaysOverdran() - 7) * 0.85; 8 return result; 9 } else { 10 return account.getDaysOverdran() * 1.75; 11 } 12 } 13 }; 14 var Account = function () { 15 this._type = new AccountType(); 16 this._daysOverdran; 17 }; 18 Account.prototype = { 19 bankCharge: function () { 20 var result = 4.5; 21 if (this._daysOverdran > 0) result += this._type.overdraftCharge(this); 22 return result; 23 } 24 };
这里也可以将daysOverdrawn作为参数传入,但是如果后面会传入多个字段,就要改代码,所以直接传入对象吧
搬移字段
搬移字段在再js程序中可能用得会多一点,在我们的程序中,某个字段被自己类之外的类多次使用的话
那么,在目标类新建一个字段,修改源字段的所有用户,让他们使用新字段
为什么这么干?
在类之间移动状态和行为,是重构过程中必不可少的措施,随着系统发展,我们会发现,自己需要新的类,并需要将现有工作责任移动新类中
但是这个星期看似合理正确的设计决策,下个星期可能就错了的情况也不是没有
如果我们发现,一个字段被其它类过多使用,那么我们就该抛弃他了
范例
继续我们上面的例子吧
1 var Account = function () { 2 this._type = new AccountType(); 3 this._interestRate; 4 }; 5 Account.prototype = { 6 interestForAmount_days: function (amount, days) { 7 return this._interestRate * amount * days / 365; 8 } 9 };
我们现在想把表示利率的_interestRate搬移到AccountType类去,目前已经有几个函数引用他了,interestForAmount_days是其中之一
所以我们现在AccountType中建立_interestRate字段与相应函数,以下是重构后的代码
1 var AccountType = function () { 2 this._interestRate; 3 }; 4 AccountType.prototype = { 5 getInterestRate: function () { 6 return this._interestRate; 7 } 8 }; 9 var Account = function () { 10 this._type = new AccountType(); 11 }; 12 Account.prototype = { 13 interestForAmount_days: function (amount, days) { 14 return this.type.getInterestRate() * amount * days / 365; 15 } 16 };
提炼类
某个类做了应该两个类做的事,那么就新建一个类,将相关的字段和函数移过去
PS:我们从开始到现在就了解了,重构的一大手段就是消除临时变量,另一个方法就是职责分离了
我们经常听到这句话,一个类应该是一个清除的抽象,处理一些明确的责任,但在实际工作中,类会不断扩张
我们会在这加一些功能,然后在那加一些数据,给某个类添加一项新任务时,你会觉得不值得为这项责任分离出一个单独的类
于是责任不断增加,这个类最后就会越来越复杂,很快这个类就连自己也不想读了
这样的类往往有大量的函数和数据,所以不好理解,这个时候我们需要考虑应该分离哪部分,并将它们写到一个单独的类中
如果这些数据和某些函数总是一起出现,某些数据经常同时变化,那么就分离吧
PS:说了这么多还是虚的,来一段代码吧
1 var Person = function () { 2 this._name; 3 this._officeAreaCode; 4 this._officeNumber; 5 }; 6 Person.prototype = { 7 getName: function () { return this._name; }, 8 getTel: function () { 9 return '(' + this._officeAreaCode + ')' + this._officeNumberl; 10 }, 11 getOfficeAreaCode: function () { 12 return this._officeAreaCode; 13 }, 14 setOfficeAreaCode: function (arg) { 15 this._officeAreaCode = arg; 16 }, 17 getOfficeNumber: function () { 18 return this._officeNumber; 19 }, 20 setOfficeNumber: function (arg) { 21 this._officeNumber = arg; 22 } 23 };
这里,我们可以将电话号码相关行为分离到一个独立的类中,优化后的代码:
1 var Tel = function () { 2 this._number; 3 this._areaCode; 4 }; 5 Tel.prototype = { 6 getAreaCode: function () { 7 return this._areaCode; 8 }, 9 setAreaCode: function (arg) { 10 this._areaCode = arg; 11 }, 12 getNumber: function () { 13 return this._number; 14 }, 15 setNumber: function (arg) { 16 this._number = arg; 17 } 18 }; 19 var Person = function () { 20 this._name; 21 this.tel = new Tel(); 22 }; 23 Person.prototype = { 24 getName: function () { return this._name; }, 25 getTel: function () { 26 return '(' + this.tel.getAreaCode() + ')' + this.tel.getNumber(); 27 } 28 };
想想,Person当然不只Tel这点属性,Tel也可能会有其它动作,如果写在一起,后面代码膨胀是可以预见的
将类内联化
如果某个类没做什么事情,那么将类消除吧,将他完成的特性移动到另一个类中,这个方法与提炼类完全相反
范例就是将上面的代码写回去......
隐藏委托关系
客户通过一个委托来调用另一个对象,在服务器上建立客户的所有函数,用以隐藏委托关系
“封装”即使不是对象最关键的特征,也是最关键的特征之一
封装意味着每个对象都应该尽可能少了解系统其它部分,如此一来,其它地方变化了,就管我屁事了(变化比较容易进行)
范例
本例中,出了人以为,多了一个部门的类啦:
1 var Department = function (manager) { 2 this._chargeCode; 3 this._manager = manager; 4 }; 5 Department.prototype = { 6 getManager: function () { 7 return this._manager; 8 } 9 }; 10 var Person = function () { 11 this._department; 12 }; 13 Person.prototype = { 14 getDepartment: function () { 15 return this._department; 16 } 17 };
如果客户想知道某人的经理是谁,他必须先取得Department对象
manager = p.getDepartment().getManager()
这样有一个问题就是,对用户揭露了Department的工作原理,于是客户指定通过Department可以追踪经理信息,如果可以隐藏Department的话,就可以减少耦合
这里就来一个委托:
1 var Department = function (manager) { 2 this._chargeCode; 3 this._manager = manager; 4 }; 5 Department.prototype = { 6 getManager: function () { 7 return this._manager; 8 } 9 }; 10 var Person = function () { 11 this._department; 12 }; 13 Person.prototype = { 14 getDepartment: function () { 15 return this._department; 16 }, 17 getManager: function () { 18 return this._department.getManager(); 19 } 20 };
这样的话,用户就不知道有Department这个东西了
移除中间人
某个类做了过多的简单委托动作,那么就让用户之间调用委托类吧
这个与上面的是反着的,各位对照着看吧
引入外加函数
我们有时需要为提供服务的类增加一个函数,但我们无法修改这个类,那么在客户类中建立一个函数,并以第一参数形式传入一个服务器类实例
1 var d = new Date(previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDay() + 1); 2 3 var d = nextDay(previousEnd); 4 function nextDay(arg) { 5 return new Date(arg.getYear(), arg.getMonth(), arg.getDay() + 1); 6 }
这种事情发生的很多,我们正在使用一个类,他提供了很多服务,但是我们突然需要一个新类,这个类却无法提供,那么我们可能会咒骂,完了修改源码
如果源码不能修改,我们就得在客户端补齐函数啦
引入本地扩展
你需要为服务类提供一些额外函数,但你无法修改这个类,建立一个新类,使他包含这些额外的函数,让这个扩展成为源类的子类或者包装类
这个与上面类似,小的要看神都龙王去了,所以暂时到这吧......