ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
### Move Field(搬移值域) (译注:本节大量保留class,method,source,target等字眼) 你的程序中,某个field(值域〕被其所驻class之外的另一个class更多地用到。 在target class 建立一个new field,修改source field的所有用户,令它们改用此new field。 ![](https://box.kancloud.cn/2016-08-15_57b1b56c20a0f.gif) **动机(Motivation)** 在classes之间移动状态(states)和行为,是重构过程中必不可少的措施。随着系统发展,你会发现自己需要新的class,并需要将原本的工作责任拖到新的class中。这个星期中合理而正确的设计决策,到了下个星期可能不再正确。这没问题;如果你从来没遇到这种情况,那才有问题。 如果我发现,对于一个field(值域),在其所驻class之外的另一个class中有更多函数使用了它,我就会考虑搬移这个field。上述所谓「使用」可能是通过设值/取值(setting/getting)函数间接进行。我也可能移动该field的用户(某函数),这取决于是否需要保持接口不受变化。如果这些函数看上去很适合待在原地,我就选择搬移field。 使用Extract Class 时,我也可能需要搬移field。此时我会先搬移field,然后再搬移函数。 **作法(Mechanics)** - 如果field的属性是public,首先使用Encapsulate Field 将它封装起来。 - 如果你有可能移动那些频繁访问该field的函数,或如果有许多函数访问某个field,先使用Self Encapsulate Field 也许会有帮助。 - 编译,测试。 - 在target class中建立与source field相同的field,并同时建立相应的设值/取值 (setting/getting)函数。 - 编译target class。 - 决定如何在source object中引用target object。 - 一个现成的field或method可以助你得到target object。如果没有,就看能否轻易建立这样一个函数。如果还不行,就得在source class中新建一个field来存放target object。这可能是个永久性修改,但你也可以暂不公开它,因为后续重构可能会把这个新建field除掉。 - 删除source field。 - 将所有「对source field的引用」替换为「对target适当函数的调用」。 - 如果是「读取」该变量,就把「对source field的引用」替换为「对target取值函数(getter)的调用」;如果是「赋值」该变量,就把对source field的引用」替换成「对设值函数(setter)的调用」。 - 如果source field不是private,就必须在source class的所有subclasses中查找source field的引用点,并进行相应替换。 - 编译,测试。 **范例(Examples)** 下面是Account class的部分代码: ~~~ class Account... private AccountType _type; private double _interestRate; double interestForAmount_days (double amount, int days) { return _interestRate * amount * days / 365; } ~~~ 我想把表示利率的_interestRate搬移到AccountType class去。目前已有数个函数引用了它,interestForAmount_days() 就是其一。下一步我要在AccountType中建立_interestRate field以及相应的访问函数: ~~~ class AccountType... private double _interestRate; void setInterestRate (double arg) { _interestRate = arg; } double getInterestRate () { return _interestRate; } ~~~ 这时候我可以编译新的AccountType class。 现在,我需要让Account class中访问此_interestRate field的函数转而使用AccountType对象,然后删除Account class中的_interestRate field。我必须删除source field,才能保证其访问函数的确改变了操作对象,因为编译器会帮我指出未正确获得修改的函数。 ~~~ private double _interestRate; double interestForAmount_days (double amount, int days) { return _type.getInterestRate() * amount * days / 365; } ~~~ **范例:使用Self Encapsulate(自我封装)** 如果有很多函数已经使用了_interestRate field,我应该先运用Self Encapsulate Field: ~~~ class Account... private AccountType _type; private double _interestRate; double interestForAmount_days (double amount, int days) { return getInterestRate() * amount * days / 365; } private void setInterestRate (double arg) { _interestRate = arg; } private double getInterestRate () { return _interestRate; } ~~~ 这样,在搬移field之后,我就只需要修改访问函数(accessors)就行了 : ~~~ double interestForAmountAndDays (double amount, int days) { return getInterestRate() * amount * days / 365; } private void setInterestRate (double arg) { _type.setInterestRate(arg); } private double getInterestRate () { return _type.getInterestRate(); } ~~~ 如果以后有必要,我可以修改访问函数(accessors)的用户,让它们使用新对象。 Self Encapsulate Field 使我得以保持小步前进。如果我需要对做许多处理,保持小步前进是有帮助的。特别值得一提的是:首先使用Self Encapsulate Field 使我得以更轻松使用Move Method 将函数搬移到target class中。如果待移函数引用了field的访问函数(accessors),那么那些引用点是无须修 改的。