java類中 多個方法求和.

悠悠隱於市發表於2011-01-28

這裡講述的是一個非常讓人尷尬的故事

我們有一個簡單的java類:

Java程式碼 複製程式碼
  1. class Details {   
  2.   double getBalance();   
  3.   double getFixed();   
  4.   double getVariable();   
  5.   double getSpendDown();   
  6.   ...   
  7.   //各種getter以及其他相關的邏輯   
  8. }  
class Details {
  double getBalance();
  double getFixed();
  double getVariable();
  double getSpendDown();
  ...
  //各種getter以及其他相關的邏輯
}



現在業務邏輯需要對一些property做求和操作,求overallBalance, overallFixed之類的。
沒什麼了不起的,一個for迴圈分分鐘搞定:

Java程式碼 複製程式碼
  1. static double getOverallBalance(Details[] arr){   
  2.   double sum = 0;   
  3.   for(int i=0; i<arr.length; i++) {   
  4.     sum += arr[i].getBalance();   
  5.   }   
  6. }  
static double getOverallBalance(Details[] arr){
  double sum = 0;
  for(int i=0; i<arr.length; i++) {
    sum += arr[i].getBalance();
  }
}



同理,對overallFixed,程式碼大同小異,copy-paste先。

Java程式碼 複製程式碼
  1. static double getOverallFixed(Details[] arr){   
  2.   double sum = 0;   
  3.   for(int i=0; i<arr.length; i++) {   
  4.     sum += arr[i].getFixed();   
  5.   }   
  6. }  
static double getOverallFixed(Details[] arr){
  double sum = 0;
  for(int i=0; i<arr.length; i++) {
    sum += arr[i].getFixed();
  }
}



這都沒什麼。可是當我寫到第七個getOverallBlahBlah(arr)函式的時候,終於有點受不了了。這程式碼重複的雖然不多,但是架不住這麼沒完沒了阿。

作為code-against-interface的推崇者,作為一個函數語言程式設計的扇子,最自然的想法就是把不同的getter邏輯抽象成一個Getter介面,如下:

Java程式碼 複製程式碼
  1. interface Getter {   
  2.   double get(Details details);   
  3. }   
  4. static double sum(Details[] arr, Getter getter){   
  5.   double sum = 0;   
  6.   for(int i=0; i<arr.length; i++) {   
  7.     sum += getter.get(arr[i]);   
  8.   }   
  9. }  
interface Getter {
  double get(Details details);
}
static double sum(Details[] arr, Getter getter){
  double sum = 0;
  for(int i=0; i<arr.length; i++) {
    sum += getter.get(arr[i]);
  }
}


娜愛思啊。有比這程式碼更優雅的麼?

然後各個求和的程式碼變成:

Java程式碼 複製程式碼
  1. double overallBalance = sum(details, new Getter(){   
  2.   public double get(Details details){   
  3.     return details.getBalance();   
  4.   }   
  5. });   
  6. double overallFixed = sum(details, new Getter(){   
  7.   public double get(Details details){   
  8.     return details.getFixed();   
  9.   }   
  10. });   
  11. ....  
double overallBalance = sum(details, new Getter(){
  public double get(Details details){
    return details.getBalance();
  }
});
double overallFixed = sum(details, new Getter(){
  public double get(Details details){
    return details.getFixed();
  }
});
....


嗯。幾乎沒有什麼重複的邏輯了。

不過......
數數程式碼行數,怎麼沒有減少,反而略有盈餘?仔細找找。發現原來的for loop是四行,現在的new Getter(){...}居然也是四行!!!
再加上一個sum()函式,我辛苦了半天的重構,居然程式碼行數增加了!

如果世界上有比一個java的匿名類的語法更臭的,那大概就是兩個匿名類語法了。據說居然還有人質疑java 7引入closure語法的意義?

另一個方法是用apache commons beanutils的getProperty(),最終的語法會是:

Java程式碼 複製程式碼
  1. double overallBalance = sum(details, "balance");  
double overallBalance = sum(details, "balance");


語法足夠簡單了,但是重構的時候就麻煩了,也沒有code-completion可用。

尷尬阿。這麼一個簡單的for loop,用匿名類重構似乎不值得。但是就任由這七個(也許回頭還會更多)長得一模一樣的for loop這麼站在這氣我?

走投無路,開始琢磨奇技淫巧了。

先宣告一個介面,來包含所有需要sum的property getter。

Java程式碼 複製程式碼
  1. private interface IDetails {   
  2.   double getBalance();   
  3.   double getFixed();   
  4.   double getVariable();   
  5.   double getSpendDown();   
  6.   ...   
  7.   //所有其它需要做sum的getter   
  8. }  
private interface IDetails {
  double getBalance();
  double getFixed();
  double getVariable();
  double getSpendDown();
  ...
  //所有其它需要做sum的getter
}



然後讓Details實現IDetails。Details的程式碼不用變。

Java程式碼 複製程式碼
  1. class Details implements IDetails {   
  2.   ...   
  3.   //程式碼不變   
  4. }  
class Details implements IDetails {
  ...
  //程式碼不變
}



戲肉來了。寫一個dynamic proxy,來封裝sum邏輯。

Java程式碼 複製程式碼
  1. static IDetails sumOf(final IDetails[] arr){   
  2.   return (IDetails)Proxy.newProxyInstance(   
  3.     getClass().getClassLoader(), new Class[]{IDetails.class}, new InvocationHandler(){   
  4.     public Object invoke(Object proxy, Method method, Object[] args)   
  5.     throws Throwable {   
  6.       double sum = 0;   
  7.       for(int i=0; i<arr.length; i++) {   
  8.         sum += ((Double)method.invoke(arr[i], args)).doubleValue();   
  9.       }   
  10.       return new Double(sum);   
  11.     }   
  12.   });   
  13. }  
static IDetails sumOf(final IDetails[] arr){
  return (IDetails)Proxy.newProxyInstance(
    getClass().getClassLoader(), new Class[]{IDetails.class}, new InvocationHandler(){
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
      double sum = 0;
      for(int i=0; i<arr.length; i++) {
        sum += ((Double)method.invoke(arr[i], args)).doubleValue();
      }
      return new Double(sum);
    }
  });
}




好了,接下來求sum的語法可以被簡化為如下:

Java程式碼 複製程式碼
  1. double overallBalance = sumOf(arr).getBalance();   
  2. double overallFixed = sumOf(arr).getFixed();   
  3. ...  
double overallBalance = sumOf(arr).getBalance();
double overallFixed = sumOf(arr).getFixed();
...



而且,再需要sum新的property,只需要把這個getter放進IDetails介面,就大功告成了。


很有趣的dynamic proxy應用。不過,一個求和這麼簡單的事情居然要動用這種奇技淫巧,很值得自豪麼?

要是在ruby裡,我就直接:

Java程式碼 複製程式碼
  1. sum(arr){balance}  
sum(arr){balance}



該死的java啊!

相關文章