重构--Extract Method(提炼函数)(四)

  • 重构方式
    • 将下面的代码放进一个独立函数中,并让函数名称解释该函数的用途

void printOwing(double amount){

printBanner();

//print details

System.out.println("name:"+_name);

System.out.println("amount"+amount);

}

void printOwing(double amount){

printBanner();

printDetails(amount);

}

  

void printDetails(double amount){

System.out.println("name:"+_name);

System.out.println("amount"+ amount);

}

  • 动机
    • 当出现一个过长的函数或者一段需要注释才能让人理解用途的代码,都可以将这段代码放进一个独立函数中;
      • 函数粒度很小,函数被复用的机会就更大;
      • 可以使高层函数读起来像一系列注释;
      • 如果函数都是细粒度,那么函数的覆写也会更容易些;
  • 做法
    • 创建一个新函数,根据这个函数的意图来对它命名(以它"做什么"来命名,而不是以它"怎样做"命名);
    • 将提炼出的代码从源函数复制到新建的目标函数中;
    • 仔细检查提炼出的代码,看看其中是否引用了"作用域限于源函数"的变量(包括局部变量和源函数参数);
    • 检查是否有"仅用于被提炼代码段"的临时变量。如果有,在目标函数中将它们声明为临时变量;
    • 检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地体谅出来。你可能需要先"分解临时变量",然后再尝试提炼。也可以用"以查询替代临时变量"将临时变量消灭掉;
    • 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数;
    • 处理完所有局部变量之后,进行编译;
    • 在源函数中,将被提炼代码段替换为对目标函数的调用;
  • 范例:无局部变量
    • 问题函数:

void printOwing(){

Enumeration e = _orders.elements();

double outstanding = 0.0;

//print banner

System.out.println("*******************");

System.out.println("***Customer Owes***");

System.out.println("*******************");

//calchlate outstanding

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

//print details

System.out.println("name:" + _name);

System.out.println("amount" + outstanding);

}

  • 提炼出"打印横幅"的代码:

void printOwing(){

Enumeration e = _orders.elements();

double outstanding = 0.0;

printBanner();

//calchlate outstanding

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

//print details

System.out.println("name:" + _name);

System.out.println("amount" + outstanding);

}

  

void printBanner() {

//print banner

System.out.println("*******************");

System.out.println("***Customer Owes***");

System.out.println("*******************");

}

  • 范例:有局部变量
    • 第一种情况:被提炼代码段只是读取这些变量的值,并不修改它们。这种情况下,可以简单地将它们当作参数传给目标函数。这种方式可以处理多个局部变量或者该变量是个对象:

void printOwing(){

Enumeration e = _orders.elements();

double outstanding = 0.0;

printBanner();

//calchlate outstanding

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

//print details

System.out.println("name:" + _name);

System.out.println("amount" + outstanding);

}

void printOwing(){

Enumeration e = _orders.elements();

double outstanding = 0.0;

printBanner();

//calchlate outstanding

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

printDetails(outstanding);

}

  

void printDetails(double outstanding) {

//print details

System.out.println("name:" + _name);

System.out.println("amount" + outstanding);

}

  • 第二种情况(对局部变量再赋值):此情况只讨论被赋值的临时变量;
    • 示例代码:

void printOwing(){

Enumeration e = _orders.elements();

double outstanding = 0.0;

printBanner();

//calchlate outstanding

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

printDetails(outstanding);

}

  • 问题1:如果源函数的参数被赋值,请马上"移除对参数的赋值"!
  • 被赋值的临时变量分两种情况:
    • 该变量只在被提炼代码段中使用。可以将这个临时变量的声明移到被提炼代码段中,然后一起提炼出去;
    • 被提炼代码段之外的代码也使用了这个变量,分两种情况:
      • 如果这个变量在被提炼代码段之后,未再被使用,只需直接在目标函数中修改它就可以了;
      • 如果被提炼代码段之后的代码还使用了这个变量,需要让目标函数返回该变量改变后的值;
  • 了解以上情况后,现在尝试将"计算"代码提炼出来:

void printOwing(){

printBanner();

double outstanding = getOutstanding();

printDetails(outstanding);

}

double getOutstanding() {

Enumeration e = _orders.elements();

double outstanding = 0.0;

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

return outstanding;

}

  • Enumeration变量e只在被提炼代码段中用到,所以可以将它整个搬到新函数中。double变量outstanding在被提炼代码段内外都被用到,所以必须让提炼出来的新函数返回它。编译通过后,把回传值改名:

double getOutstanding() {

Enumeration e = _orders.elements();

double result = 0.0;

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

result += each.getAmount();

}

return result;

}

  • outstanding变量只是很单纯地被初始化为一个明确初值,所以可以只在新函数中对它初始化。如果代码还对这个变量做了其他处理,就必须将它的值作为参数传给目标函数。对于这种变化,最初的代码可能是这样:

void printOwing(double previousAmount){

Enumeration e = _orders.elements();

double outstanding = previousAmount * 1.2;

printBanner();

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

outstanding += each.getAmount();

}

printDetails(outstanding);

}

  • 提炼代码:

void printOwing(double previousAmount){

double outstanding = previousAmount * 1.2;

printBanner();

outstanding = getOutstanding(outstanding);

printDetails(outstanding);

}

double getOutstanding(double initialValue) {

double result = 0.0;

Enumeration e = _orders.elements();

while(e.hasMoreElements()){

Order each = (Order)e.nextElement();

result += each.getAmount();

}

return result;

}

  • 通过编译后,将变量outstanding的初始化过程进行整理:

void printOwing(double previousAmount){

printBanner();

double outstanding = getOutstanding(previousAmount * 1.2);

printDetails(outstanding);

}

  • 问题:需要返回的变量不止一个,该怎么办?
    • 挑选另一块代码来提炼。尽可能让函数都只返回一个值,用多个函数来返回多个值;
  • 写在最后
    • 临时变量往往为数众多。这种情况下,可以尝试"以查询取代临时变量"的方式减少临时变量。如果这样做无效,可以尝试"以函数对象取代函数";
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值