论坛首页 Java企业应用论坛

奇技淫巧?

浏览 46462 次
锁定老帖子 主题:奇技淫巧?
该帖已经被评为良好帖
作者 正文
   发表时间:2006-12-20  
OO
这里讲述的是一个非常让人尴尬的故事

我们有一个简单的java类:
class Details {
  double getBalance();
  double getFixed();
  double getVariable();
  double getSpendDown();
  ...
  //各种getter以及其他相关的逻辑
}


现在业务逻辑需要对一些property做求和操作,求overallBalance, overallFixed之类的。
没什么了不起的,一个for循环分分钟搞定:
static double getOverallBalance(Details[] arr){
  double sum = 0;
  for(int i=0; i<arr.length; i++) {
    sum += arr[i].getBalance();
  }
}


同理,对overallFixed,代码大同小异,copy-paste先。
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接口,如下:
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]);
  }
}

娜爱思啊。有比这代码更优雅的么?

然后各个求和的代码变成:
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(),最终的语法会是:
double overallBalance = sum(details, "balance");

语法足够简单了,但是重构的时候就麻烦了,也没有code-completion可用。

尴尬阿。这么一个简单的for loop,用匿名类重构似乎不值得。但是就任由这七个(也许回头还会更多)长得一模一样的for loop这么站在这气我?

走投无路,开始琢磨奇技淫巧了。

先声明一个接口,来包含所有需要sum的property getter。
private interface IDetails {
  double getBalance();
  double getFixed();
  double getVariable();
  double getSpendDown();
  ...
  //所有其它需要做sum的getter
}


然后让Details实现IDetails。Details的代码不用变。
class Details implements IDetails {
  ...
  //代码不变
}


戏肉来了。写一个dynamic proxy,来封装sum逻辑。
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的语法可以被简化为如下:
double overallBalance = sumOf(arr).getBalance();
double overallFixed = sumOf(arr).getFixed();
...


而且,再需要sum新的property,只需要把这个getter放进IDetails接口,就大功告成了。


很有趣的dynamic proxy应用。不过,一个求和这么简单的事情居然要动用这种奇技淫巧,很值得自豪么?

要是在ruby里,我就直接:
sum(arr){balance}


该死的java啊!
   发表时间:2006-12-20  
长见识了.
0 请登录后投票
   发表时间:2006-12-20  
能用,,,具体用来干什么,需要自己写代码实现..



在你自己的 servlet ,覆写javax.servlet.http.HttpServlet 里的方法实现自己需要的功能

public doPut(request,response){
///do something

}

public doDelete(request,response){
///do something

}
0 请登录后投票
   发表时间:2006-12-20  
发错,,,不好意思
0 请登录后投票
   发表时间:2006-12-20  
昨天看了一个smalltalk的简单介绍,的确很为这种语言的灵活性所震惊

像楼主所描述的技巧,在很多脚本语言里都很常见

我的疑惑就是这些技巧所带来的好处和这些技巧对于入门难度的影响

哪个更大一些

0 请登录后投票
   发表时间:2006-12-20  
如果把Details改成Map的话就简单多了,
如果非要用XO的话,用反射也是很方便的手段。
0 请登录后投票
   发表时间:2006-12-20  
有点意思。
0 请登录后投票
   发表时间:2006-12-20  
引用
要是在ruby里,我就直接:
代码

   1. sum(arr){balance} 


上面的,和这个。

引用
# 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]); 
#   } 


是否有着相同的含义呢?

Ruby等动态语言,其接口是隐式的,而且Ruby的 “代码块”, 相对于Java的匿名类,去掉了所有不必要的东西:
1. new 和 接口,或基类名称不必要,因为接口是隐式的, 大家心中有数即可
2. 方法申明也不需要, 整个东东就是一个叫 yield 的方法

所以,简化到最后,就成为 { ... } 了

嘿嘿, 还有, 我觉得重复不是看简单的代码相似, 而应该从语义,或逻辑角度上去抽像。

比如上面的: 一开始像是在说,
  求Detail集合 属性一的和
  求Detail集合 属性二的和

那是否有必要抽像成:
  求Detail集合某属性的和

而你在建模的时候, 各属性都是具体的,意味着是分开的。
却是必要的。

所以上一层抽像是否有必要呢?

我觉得其实一开始的。简单的for 挺好。。

“让代码保持简单”

因为 for  语句里没有其他复杂的逻辑, 只是求和。所以这不会有什么危害。

0 请登录后投票
   发表时间:2006-12-20  
怎么都是“静态”方法啊?

在ruby里一般是
overall_balance = arr.inject(0) {|s,x| s += x.balance}


或者给arr添加sum,当然添加给Array也行
def arr.sum(f)
  self.inject(0) {|s,x| s += x.send(f)}
end

overall_balance = arr.sum(:balance)
overall_variable = arr.sum(:variable)


在java里
class Summary {
  private double overallBalance = 0;
  private double overallFixed = 0;
  private double overallVariable = 0;
  private double overallSpendDown = 0;

  public Summary(Details[] arr) {
    for (int i = 0; i < arr.length; i++) {
      overallBalance += arr[i].getBalance();
      overallFixed += arr[i].getFixed();
      overallVariable += arr[i].getVariable();
      overallSpendDown += arr[i].getSpendDown();
    }
  }

  // getters
  //...
}


因为这么做太简单所以不愿意用?还是一定要用Java去模仿Ruby?
0 请登录后投票
   发表时间:2006-12-20  
bencode 写道

我觉得其实一开始的。简单的for 挺好。。

“让代码保持简单”

因为 for  语句里没有其他复杂的逻辑, 只是求和。所以这不会有什么危害。


就是这个话。

所以我说这是一个尴尬的情况。

我猜测,对这个问题的取舍不同人都有不同的倾向。很难说谁对谁错。要说错,也是高司令的错。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics