ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
反向重构:隐藏委托关系(189) ![](https://box.kancloud.cn/4079fc6c805eb5098772f6a8d39f71c2_408x420.jpeg) ``` manager = aPerson.manager; class Person {  get manager() {return this.department.manager;} ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) `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),一口气替换它的所有调用点。