`
ajoo
  • 浏览: 449859 次
社区版块
存档分类
最新评论
阅读更多
这里讲述的是一个非常让人尴尬的故事

我们有一个简单的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啊!
分享到:
评论
64 楼 CrayEye 2007-04-03  
楼主的作法很巧妙 但是还是不喜欢。 怎么说呢 我不太会表述 不自然吧。如果让我们用自然语言去描述的话肯定不会这么说的。 sumOf(arr).getXXXX 

我的理解 这是一个aop。 sumOf 截获Idetails接口中的getXXXX函数 改变其语意 使其变成了求和函数。

aop不应该是这么用的吧。而且就使用者来说 调用一个本来是取属性值的getXXXX函数却可以得到sum值。会让人感觉很magic 很不容易理解。

ruby应该是类似把属性放入hashmap的做法 而不是楼主的方法二Getter吧。



顺便问 如果在C++里遇到这一问题 最自然的想法应该是用宏吧?
63 楼 dovecat 2007-04-02  
ray_linn 写道
说实话 Dy那个什么proxy十分的丑陋,是我见过的最丑的东西之一. 而且proxy这个词又十分含糊,鬼知道这里突然出现这个proxy,是什么原因什么由来,是不是天外又飞仙了?

这种需求,反射是我马上能想到的东西,

个人觉得EastSun的代码,兼顾的简洁和易读二者的平衡.LZ那个代码换个人,估计就得浪费一个早上去看堆资料,而且行数比反射只多不少.

那种感觉,真的就象柳永的词,卖弄的成分多,实用的成分少.


反射的话,比dyna proxy要更加清楚明确.
不过,话说回来,"酒窝"这东西还是挺可爱的.
62 楼 samhe 2007-04-02  
恩,一直都想搞个类似的东西.哈哈...现在有现成的了.
61 楼 jellyfish 2007-03-04  
java.lang.Number
60 楼 jianfeng008cn 2007-03-03  
又看了一下,感觉对一个程序员来讲,楼主的思路应该很容易理解才合理,至于涉及架构什么的,一般人我还用不到它。

引用

具体实现代码不写了,熟悉beanutils的都知道,不熟悉的,看看javadoc也就明白了(顺便说一下,不要什么东西都动不动就用“学习”两个字,beanutils也要学?pico也要学?是不是街角新开的洗脚房也需要“学习”一下?很让我有一种一个带着眼睛的三十岁左右男子,在和小姐commit transaction之前,在屋里到处细细索索地翻找东西,小姐问说你找什么?回答说要找到manual或者tutorial或者21天精通之类的书先“学习”一下的联想。)


说得过分了,庸俗了,看javadoc就不是学习拉
59 楼 shiwentao1982 2007-03-03  
没用过这种技巧,学习!
58 楼 dearmite 2007-03-03  
method.invoke(arr[i], args)).

Details.class.getMethod(name);  
其实思路是差不多的,都是得到你的类的method.
但是第一种可以用类来传递,
第二种就只可以用字符串来传。

为什么大家对第二种感觉不错,
更有人用JS来弄。

而对楼主的第一种思路不感冒呢?
请大家拿出当初学习getMethod的这种精神来重新审视一下楼主的代码。
和自己是否对新生的JAVA写法有些抵触。

我和楼主的感觉是差不多的。
要么使用for .
要么使用代理。

这两种都有一个共性就是可以重构。



57 楼 dearmite 2007-03-03  
dreamstone 写道
看了文章感觉很有意思,作者的思维挺好玩,感觉如果平时应用开发还是不要这样的好,如果是写一些特殊的需求,例如框架什么的还是不错的。另外再提供一个思路:
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
for(int i=0;i<arr.length;i++){
    engine.put("obj", arr[i]);
    Object obj = engine.eval("obj."+method);
    if (obj instanceof Double){
        sum += ((Double)obj).doubleValue();
    }
}

是用jdk1.6中的script就可以简单实现了,好处是写起来方便,学习成本低。坏处是也存在重构问题,并且内部实现和动态代理、反射等也差不哪里去。


利用JS的字符串与类的弱混淆来达到把类来“数组化”这一点。
好象感觉上还没有
getMethod("")来的直观些呢。
这种方法猛滴一看,
以为进了JSP的JAVA代码里了呢?

再说,这种代码的效率利用JS的engine,高不到哪去吧?

我上面说了,不是财务系统,通用化的不值。
如果是财务系统。
用脚本来替JAVA语言,
大哥,你还是直接杀了我算了。

我搞过一段时间的性能测试的。这样的做法会让我crazy

56 楼 dearmite 2007-03-03  
ajoo 写道
继续我们的奇技淫巧之旅。




double totalBalance = sumOf(accountArray, "balance");
double totalRate = sumOf(accountArray, "rate");
double totalReturnOfInvestment = sumOf(accountArray, "returnOfInvestment");



4。于是,就出现了我们慕容家的“斗转星移”神功了。就像做梦一样,你可以这样写了:
double totalBalance = sumOf(accountArray).getBalance();
double totalRoi = sumOf(accountArray).getReturnOfInvestment();

作为一个客户,我几乎无法挑出什么毛病了,所有的Eclipse提供的拐棍都还在,语法也是简练自然得象自然语言。




感觉不错,
其实什么是“模式”有两个特点,
一个就是应用起来简单一些,
一个就是适用比较广。
这两点,
楼主的技巧都具备了。第一是可以重构。使用字符串的重构尽管有选项,但是大家都知道是怎么回事。
尤其是用过hibernate的。
那么,不管这个技巧本身多复杂,挺多是这个JAVA的JAVADOC很长,很长而已。
你又不用改变这个JAVA文件,
为什么大家就不认同这个方法呢,
不知大家看过JBOSS的一些常规包的JAVA文件没。

那上面的代码就不“花”么?我想比楼主的还要再花上一套吧?
不管怎么说,
先是赞楼主一个。

但是,话说回来,如果项目里只有3处以下的类要用到这个求和。
我还是喜欢for
但是,要真的是个财务系统。
有300个这种求和呢?
不光是我不喜欢for了吧?
那公司里正好有一个人写了楼主这么“优美”的“模式”,但是只给你一个JAR包。没有JAVA文件
而且此人正是你所**才可见的高人之一,

你可能巴不得把他的代码反编译过来,然后研究而后快呢。

呵呵。


建议楼主把此JAVA文件,打成JAR包,但是JAR包里面放上一份TXT说明再放上一个JAVA文件。
JAR包的名字就叫 financeSum.jar

并冠以公司财务系统高级架构包一员的美名。

那么研究之人肯定比现在要多。
:)


55 楼 glassprogrammer 2007-03-02  
huhu, 我也赞成第二种方法是标准解耦的方法. 不过奇淫技巧也是很有价值的, 人类历史上的重大发明, 开始的时候还不都是奇淫技巧.
54 楼 intolong 2007-03-01  
第二种方法是标准解耦的方法。其他的才是奇淫技巧
53 楼 yyzheng 2007-03-01  
ajoo 写道
yyzheng 写道
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。


理解万岁。呵呵。

不过,这些新的接口是不要求被sum的类来"implements"的。因为“酒窝”可以搞定这一切。所以,不能算“侵入”吧?


哦,我又看了一下,可以不用在原先系统架构的那些废接口上做文章,直接添加新的接口即可,这样的话我说的:“因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构”就不成立了,看来“酒窝”是个好东东,有时间要好好研究一下:)

我这里说的侵入含义比较广,不仅仅是强迫应用类实现某些特定的接口或继承某些特定的基类,当然,按照这种概念,只要你用到了某种框架,其实都或多或少算作“侵入”了。但作为一个框架,提供给应用层的接口很重要,好的框架提供的侵入方式很自然,就像spring。

新增的这些接口仅仅是为了用到重构后的模块而加的,体现的是一种框架的配置语义,这样的话怎么组织这些东东以提供更clean的接口给团队其他程序员就很重要了,不然其他人会抱怨的:)

其实我觉得你提出的第二种Getter接口的方法就已经很好了,面向接口、简单实用、在架构上也很清晰,做应用的话写到这种程度就已经足够了,匿名内部类其实也很简单,而且有编程工具的支持,虽然代码行数没少多少,但从整体架构上更清楚了。
52 楼 intolong 2007-03-01  
whyang 写道
搞那么复杂干啥

看很简洁

static double getOverallBalance(Details[] arr){   

      Details sumDetails=new Details();
 
      for(int i=0; i<arr.length; i++) {   
        sumDetails.setBalance()=sumDetails.getBalance()+arr[i].getBalance();   
        sumDetails.setFixed()=sumDetails.getFixed()+ arr[i].getFixed();   
        sumDetails.setVariable()=sumDetails.getVariable()+ arr[i].getVariable();   
        sumDetails.setSpendDown()=sumDetails.getSpendDown()+ arr[i].getSpendDown();   
      }   
} 



人家只想要getBlance(),你给这老多
51 楼 whyang 2007-03-01  
搞那么复杂干啥

看很简洁

static double getOverallBalance(Details[] arr){   

      Details sumDetails=new Details();
 
      for(int i=0; i<arr.length; i++) {   
        sumDetails.setBalance()=sumDetails.getBalance()+arr[i].getBalance();   
        sumDetails.setFixed()=sumDetails.getFixed()+ arr[i].getFixed();   
        sumDetails.setVariable()=sumDetails.getVariable()+ arr[i].getVariable();   
        sumDetails.setSpendDown()=sumDetails.getSpendDown()+ arr[i].getSpendDown();   
      }   
} 
50 楼 sandy 2007-03-01  
这种情况,还是借助于工具自动生成代码技术。

用什么反射,效率低,代码也很难看
我宁愿重复
49 楼 ajoo 2007-03-01  
yyzheng 写道
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。


理解万岁。呵呵。

不过,这些新的接口是不要求被sum的类来"implements"的。因为“酒窝”可以搞定这一切。所以,不能算“侵入”吧?
48 楼 yyzheng 2007-02-28  
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
47 楼 dreamstone 2007-02-27  
看了文章感觉很有意思,作者的思维挺好玩,感觉如果平时应用开发还是不要这样的好,如果是写一些特殊的需求,例如框架什么的还是不错的。另外再提供一个思路:
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
for(int i=0;i<arr.length;i++){
    engine.put("obj", arr[i]);
    Object obj = engine.eval("obj."+method);
    if (obj instanceof Double){
        sum += ((Double)obj).doubleValue();
    }
}

是用jdk1.6中的script就可以简单实现了,好处是写起来方便,学习成本低。坏处是也存在重构问题,并且内部实现和动态代理、反射等也差不哪里去。
46 楼 yiding_he 2007-02-27  
思路新颖,快刀斩乱麻!
45 楼 qingjian 2007-02-27  
感觉 lz好痛苦

相关推荐

    Python 语言有什么奇技淫巧吗?.docx

    Python 语言有什么奇技淫巧吗?.docxPython 语言有什么奇技淫巧吗?.docxPython 语言有什么奇技淫巧吗?.docxPython 语言有什么奇技淫巧吗?.docxPython 语言有什么奇技淫巧吗?.docxPython 语言有什么奇技淫巧吗?....

    Python那些事——python的奇技淫巧

    《Python那些事——python的奇技淫巧!》python2.7版本

    算法心得-高效算法的奥秘(原书第2版)_带书签_高清_[位运算的奇技淫巧].pdf

    算法心得-高效算法的奥秘(原书第2版)_带书签_高清_[位运算的奇技淫巧].pdf.

    来自小密圈里的那些奇技淫巧.pdf

    EVAL长度限制突破技巧 命令长度限制突破技巧 Mysql突破换行符的技巧 命令执行WAF绕过技巧 无字母数字Webshell构造技巧

    渗透中关于dns的奇技淫巧.pdf

    DNS服务作为网络的一种基础架构,在网络中有举足轻重的地位。它担负着整个网络用户计算机的名称解析工作。没有正确的名称解析,服务器就无法识别各客户机。我们日常进行的浏览网页等上网活动,无一例外都在使用DNS...

    bash奇技淫巧

    bash奇技淫巧,长见识

    Python-收集到的一些src挖掘奇技淫巧

    收集到的一些src挖掘奇技淫巧

    Java 8中Stream API的这些奇技淫巧!你Get了吗?

    主要介绍了Java 8中Stream API的这些奇技淫巧!你Get了吗?文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    RocketMQ 奇技淫巧之 ServiceLoader 源码解读 - GitChat

    RocketMQ 奇技淫巧之 ServiceLoader 源码解读 抓下来打包成了HTML文件, 方便离线观看

    CSS中边框使用负边距值的奇技淫巧

    主要介绍了CSS中边框使用负边距值的奇技淫巧,文中介绍了使用负边距进行布局以及实现重叠等效果的方法,非常之巧妙,需要的朋友可以参考下

    移动端Ui自动化上的正经奇技淫巧Havefun

    移动端Ui自动化上的一些“奇技淫巧”;工作上总结的一些好用的东西在这里分享给大家 : )

    Git的奇技淫巧.zip

    Git是一个 “分布式版本管理工具”,简单的理解版本管理工具:大家在写东西的时候都用过 “回撤” 这个 功能,但是回撤只能回撤几步,假如想要找回我三天之前的修改,光用 “回撤” 是找不回来的。...

    Go-Git的奇技淫巧

    Git是一个“分布式版本管理工具”,简单的理解版本管理工具:大家在写东西的时候都用过“回撤”这个功能,但是回撤只能回撤几步,假如想要找回我三天之前的修改,光用“回撤”是找不回来的。

    大幅优化MySQL查询性能的奇技淫巧

    主要介绍了大幅优化MySQL查询性能的方法,作者根据实际运行时间比对分析了InnoDB等几个重要的MySQL性能优化点,极力推荐!需要的朋友可以参考下

    c/c++ 奇技淫巧(一些c语言的技巧)

    一. 变长数组 严格说来,变长数组的实现在c++中并不是一件麻烦的事情。Stl中的vector本身就是一个变长数组,并且有自动管理内存的能力。 但是在c中,实现变长数组就稍显麻烦。用C实现,必然需要一个结构,结构当中...

    C语言中的奇技淫巧

    前言 学习C语言的过程中,总会遇到很多令人眼前一亮的代码,尤其是你写了几十行的代码,别人只用了简单几行的递归就实现的功能。下面我就总结几个C语言中 比较新手向的代码。让你有一种”woc!还能这么写!...

    输入输入的2个奇技淫巧1

    原来而cin,cout之所以效率低,是因为先把要输出的东西存入缓冲区,再输出,导致效率降低,而这段语句可以来打消iostream的输入 输出缓存,可以节省许多时

    JavaScript奇技淫巧45招 _ 不可能不确定1

    1、首次为变量赋值时务必使用var关键字 2、使用===取代== 3、underfined、null、0、false、NaN、空字符串的逻辑结果均为false

Global site tag (gtag.js) - Google Analytics