反向重构:隐藏委托关系(189)
manager = aPerson.manager;
class Person {
get manager() {return this.department.manager;}
manager = aPerson.department.manager;动机
在隐藏委托关系(189)的“动机”一节中,我谈到了“封装受托对象”的好处。但是这层封装也是有代价的。每当客户端要使用受托类的新特性时,你就必须在服务端添加一个简单委托函数。随着受托类的特性(功能)越来越多,更多的转发函数就会使人烦躁。服务类完全变成了一个中间人(81),此时就应该让客户直接调用受托类。(这个味道通常在人们狂热地遵循迪米特法则时悄然出现。我总觉得,如果这条法则当初叫作“偶尔有用的迪米特建议”,如今能少很多烦恼。)
很难说什么程度的隐藏才是合适的。还好,有了隐藏委托关系(189)和删除中间人,我大可不必操心这个问题,因为我可以在系统运行过程中不断进行调整。随着代码的变化,“合适的隐藏程度”这个尺度也相应改变。6 个月前恰如其分的封装,现今可能就显得笨拙。重构的意义就在于:你永远不必说对不起——只要把出问题的地方修补好就行了。
做法
为受托对象创建一个取值函数。
对于每个委托函数,让其客户端转为连续的访问函数调用。每次替换后运行测试。
替换完委托方法的所有调用点后,你就可以删掉这个委托方法了。
这能通过可自动化的重构手法来完成,你可以先对受托字段使用封装变量(132),再应用内联函数(115)内联所有使用它的函数。
范例
我又要从一个 Person 类开始了,这个类通过维护一个部门对象来决定某人的经理是谁。(如果你一口气读完本书的好几章,可能会发现每个“人与部门”的例子都出奇地相似。)
客户端代码…
manager = aPerson.manager;class Person…
get manager() {return this._department.manager;}class Department…
get manager() {return this._manager;}像这样,使用和封装 Department 都很简单。但如果大量函数都这么做,我就不得不在 Person 之中安置大量委托行为。这就该是移除中间人的时候了。首先在 Person 中建立一个函数,用于获取受托对象。
class Person…
get department() {return this._department;}然后逐一处理每个客户端,使它们直接通过受托对象完成工作。
客户端代码…
manager = aPerson.department.manager;完成对客户端引用点的替换后,我就可以从 Person 中移除 manager 方法了。我可以重复此法,移除 Person 中其他类似的简单委托函数。
我可以混用两种用法。有些委托关系非常常用,因此我想保住它们,这样可使客户端代码调用更友好。何时应该隐藏委托关系,何时应该移除中间人,对我而言没有绝对的标准——代码环境自然会给出该使用哪种手法的线索,具备思考能力的程序员应能分辨出何种手法更佳。
如果手边在用自动化的重构工具,那么本手法的步骤有一个实用的变招:我可以先对 department 应用封装变量(132)。这样可让 manager 的取值函数调用 department 的取值函数。
class Person…
get manager() {return this.department.manager;}在 JavaScript 中,调用取值函数的语法跟取用普通字段看起来很像,但通过移除 department 字段的下划线,我想表达出这里是调用了取值函数而非直接取用字段的区别。
然后我对 manager 方法应用内联函数(115),一口气替换它的所有调用点。