🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
反向重构:以命令取代函数(337) ![](https://box.kancloud.cn/2c9f864a38511ce37003705d135ebab4_532x207.jpeg) ``` class ChargeCalculator {  constructor (customer, usage){   this._customer = customer;   this._usage = usage;  }  execute() {   return this._customer.rate * this._usage;  } } ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` function charge(customer, usage) { return customer.rate * usage; } ``` ### 动机 命令对象为处理复杂计算提供了强大的机制。借助命令对象,可以轻松地将原本复杂的函数拆解为多个方法,彼此之间通过字段共享状态;拆解后的方法可以分别调用;开始调用之前的数据状态也可以逐步构建。但这种强大是有代价的。大多数时候,我只是想调用一个函数,让它完成自己的工作就好。如果这个函数不是太复杂,那么命令对象可能显得费而不惠,我就应该考虑将其变回普通的函数。 ### 做法 - 运用提炼函数(106),把“创建并执行命令对象”的代码单独提炼到一个函数中。 > 这一步会新建一个函数,最终这个函数会取代现在的命令对象。 - 对命令对象在执行阶段用到的函数,逐一使用内联函数(115)。 > 如果被调用的函数有返回值,请先对调用处使用提炼变量(119),然后再使用内联函数(115)。 - 使用改变函数声明(124),把构造函数的参数转移到执行函数。 - 对于所有的字段,在执行函数中找到引用它们的地方,并改为使用参数。每次修改后都要测试。 - 把“调用构造函数”和“调用执行函数”两步都内联到调用方(也就是最终要替换命令对象的那个函数)。 - 测试。 - 用移除死代码(237)把命令类消去。 ### 范例 假设我有一个很小的命令对象。 ``` class ChargeCalculator {  constructor (customer, usage, provider){   this._customer = customer;   this._usage = usage;   this._provider = provider;  }  get baseCharge() {   return this._customer.baseRate * this._usage;  }  get charge() {   return this.baseCharge + this._provider.connectionCharge;  } } ``` 使用方的代码如下。 ##### 调用方... `monthCharge = new ChargeCalculator(customer, usage, provider).charge;`命令类足够小、足够简单,变成函数更合适。 首先,我用提炼函数(106)把命令对象的创建与调用过程包装到一个函数中。 ##### 调用方... `monthCharge = charge(customer, usage, provider);`##### 顶层作用域... ``` function charge(customer, usage, provider) { return new ChargeCalculator(customer, usage, provider).charge; } ``` 接下来要考虑如何处理支持函数(也就是这里的`baseCharge`函数)。对于有返回值的函数,我一般会先用提炼变量(119)把返回值提炼出来。 ##### class ChargeCalculator... ``` get baseCharge() { return this._customer.baseRate * this._usage; } get charge() { const baseCharge = this.baseCharge; return baseCharge + this._provider.connectionCharge; } ``` 然后对支持函数使用内联函数(115)。 ##### class ChargeCalculator... ``` get charge() { const baseCharge = this._customer.baseRate * this._usage; return baseCharge + this._provider.connectionCharge; } ``` 现在所有逻辑处理都集中到一个函数了,下一步是把构造函数传入的数据移到主函数。首先用改变函数声明(124)把构造函数的参数逐一添加到`charge`函数上。 ##### class ChargeCalculator... ``` constructor (customer, usage, provider){  this._customer = customer;  this._usage = usage;  this._provider = provider; } charge(customer, usage, provider) {  const baseCharge = this._customer.baseRate * this._usage;  return baseCharge + this._provider.connectionCharge; } ``` ##### 顶层作用域... ``` function charge(customer, usage, provider) { return new ChargeCalculator(customer, usage, provider) .charge(customer, usage, provider); } ``` 然后修改`charge`函数的实现,改为使用传入的参数。这个修改可以小步进行,每次使用一个参数。 ##### class ChargeCalculator... ``` constructor (customer, usage, provider){  this._customer = customer;  this._usage = usage;  this._provider = provider; } charge(customer, usage, provider) {  const baseCharge = customer.baseRate * this._usage;  return baseCharge + this._provider.connectionCharge; } ``` 构造函数中对`this._customer`字段的赋值不删除也没关系,因为反正没人使用这个字段。但我更愿意去掉这条赋值语句,因为去掉它以后,如果在函数实现中漏掉了一处对字段的使用没有修改,测试就会失败。(如果我真的犯了这个错误而测试没有失败,我就应该考虑增加测试了。) 其他参数也如法炮制,直到`charge`函数不再使用任何字段: ##### class ChargeCalculator... ``` charge(customer, usage, provider) { const baseCharge = customer.baseRate * usage; return baseCharge + provider.connectionCharge; } ``` 现在我就可以把所有逻辑都内联到顶层的`charge`函数中。这是内联函数(115)的一种特殊情况,我需要把构造函数和执行函数一并内联。 ##### 顶层作用域... ``` function charge(customer, usage, provider) { const baseCharge = customer.baseRate * usage; return baseCharge + provider.connectionCharge; } ``` 现在命令类已经是死代码了,可以用移除死代码(237)给它一个体面的葬礼。