【重构笔记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);
3 var d = nextDay(previousEnd);
4 function nextDay(arg) {
5     return new Date(arg.getYear(), arg.getMonth(), arg.getDay() + 1);
6 }
这种事情发生的很多,我们正在使用一个类,他提供了很多服务,但是我们突然需要一个新类,这个类却无法提供,那么我们可能会咒骂,完了修改源码

如果源码不能修改,我们就得在客户端补齐函数啦

引入本地扩展

你需要为服务类提供一些额外函数,但你无法修改这个类,建立一个新类,使他包含这些额外的函数,让这个扩展成为源类的子类或者包装类

这个与上面类似,小的要看神都龙王去了,所以暂时到这吧......



本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3378915.html,如需转载请自行联系原作者

相关文章
|
7月前
|
程序员
实例告诉你如何把 if-else 重构成高质量代码
实例告诉你如何把 if-else 重构成高质量代码
63 0
|
5月前
软件复用问题之衡量是否应该复制或复用代码,如何解决
软件复用问题之衡量是否应该复制或复用代码,如何解决
|
4月前
|
SQL 分布式计算 大数据
Android项目架构设计问题之平衡技术选型与业务需求之间的关系如何解决
Android项目架构设计问题之平衡技术选型与业务需求之间的关系如何解决
65 0
|
5月前
软件复用问题之在哪些情况下,复制可能是一个更好的选择
软件复用问题之在哪些情况下,复制可能是一个更好的选择
|
6月前
软件的质量特性及其子特性快速记忆表
软件的质量特性及其子特性快速记忆表
78 0
|
设计模式 Java
JAVA设计模式7:适配者模式,彻底解决两不兼容接口之间的问题
JAVA设计模式7:适配者模式,彻底解决两不兼容接口之间的问题
173 0
|
数据库
重构——前提工作
重构——前提工作
|
消息中间件 设计模式 缓存
实例告诉你如何把 if-else 重构成高质量代码!
为什么我们写的代码都是 if-else? 异常逻辑处理型重构方法实例一 异常逻辑处理型重构方法实例二 异常逻辑处理型重构方法实例三 异常逻辑处理型重构方法实例四 状态处理型重构方法实例一 状态处理型重构方法实例二 总结
|
资源调度 前端开发 安全
5G 逻辑架构的重构 | 带你读《5G时代的承载网》之十一
5G 的架构设计主要需要满足关键性能需求和网络运营需求,为便于理解 5G 架构设计的革新,本节将以现有 4G 的网络架构为基础,逐一分析现有架 构的局限性,对比 5G 新的性能需求和运营需求,对现有架构进行分解、重构, 逐步靠近 3GPP 确定的 5G 新架构。
5G 逻辑架构的重构   | 带你读《5G时代的承载网》之十一