2006-12-20
奇技淫巧?
这里讲述的是一个非常让人尴尬的故事
我们有一个简单的java类:
现在业务逻辑需要对一些property做求和操作,求overallBalance, overallFixed之类的。
没什么了不起的,一个for循环分分钟搞定:
同理,对overallFixed,代码大同小异,copy-paste先。
这都没什么。可是当我写到第七个getOverallBlahBlah(arr)函数的时候,终于有点受不了了。这代码重复的虽然不多,但是架不住这么没完没了阿。
作为code-against-interface的推崇者,作为一个函数式编程的扇子,最自然的想法就是把不同的getter逻辑抽象成一个Getter接口,如下:
娜爱思啊。有比这代码更优雅的么?
然后各个求和的代码变成:
嗯。几乎没有什么重复的逻辑了。
不过......
数数代码行数,怎么没有减少,反而略有盈余?仔细找找。发现原来的for loop是四行,现在的new Getter(){...}居然也是四行!!!
再加上一个sum()函数,我辛苦了半天的重构,居然代码行数增加了!
如果世界上有比一个java的匿名类的语法更臭的,那大概就是两个匿名类语法了。据说居然还有人质疑java 7引入closure语法的意义?
另一个方法是用apache commons beanutils的getProperty(),最终的语法会是:
语法足够简单了,但是重构的时候就麻烦了,也没有code-completion可用。
尴尬阿。这么一个简单的for loop,用匿名类重构似乎不值得。但是就任由这七个(也许回头还会更多)长得一模一样的for loop这么站在这气我?
走投无路,开始琢磨奇技淫巧了。
先声明一个接口,来包含所有需要sum的property getter。
然后让Details实现IDetails。Details的代码不用变。
戏肉来了。写一个dynamic proxy,来封装sum逻辑。
好了,接下来求sum的语法可以被简化为如下:
而且,再需要sum新的property,只需要把这个getter放进IDetails接口,就大功告成了。
很有趣的dynamic proxy应用。不过,一个求和这么简单的事情居然要动用这种奇技淫巧,很值得自豪么?
要是在ruby里,我就直接:
该死的java啊!
我们有一个简单的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啊!
评论
CrayEye
2007-04-03
楼主的作法很巧妙 但是还是不喜欢。 怎么说呢 我不太会表述 不自然吧。如果让我们用自然语言去描述的话肯定不会这么说的。 sumOf(arr).getXXXX
我的理解 这是一个aop。 sumOf 截获Idetails接口中的getXXXX函数 改变其语意 使其变成了求和函数。
aop不应该是这么用的吧。而且就使用者来说 调用一个本来是取属性值的getXXXX函数却可以得到sum值。会让人感觉很magic 很不容易理解。
ruby应该是类似把属性放入hashmap的做法 而不是楼主的方法二Getter吧。
顺便问 如果在C++里遇到这一问题 最自然的想法应该是用宏吧?
我的理解 这是一个aop。 sumOf 截获Idetails接口中的getXXXX函数 改变其语意 使其变成了求和函数。
aop不应该是这么用的吧。而且就使用者来说 调用一个本来是取属性值的getXXXX函数却可以得到sum值。会让人感觉很magic 很不容易理解。
ruby应该是类似把属性放入hashmap的做法 而不是楼主的方法二Getter吧。
顺便问 如果在C++里遇到这一问题 最自然的想法应该是用宏吧?
dovecat
2007-04-02
ray_linn 写道
说实话 Dy那个什么proxy十分的丑陋,是我见过的最丑的东西之一. 而且proxy这个词又十分含糊,鬼知道这里突然出现这个proxy,是什么原因什么由来,是不是天外又飞仙了?
这种需求,反射是我马上能想到的东西,
个人觉得EastSun的代码,兼顾的简洁和易读二者的平衡.LZ那个代码换个人,估计就得浪费一个早上去看堆资料,而且行数比反射只多不少.
那种感觉,真的就象柳永的词,卖弄的成分多,实用的成分少.
这种需求,反射是我马上能想到的东西,
个人觉得EastSun的代码,兼顾的简洁和易读二者的平衡.LZ那个代码换个人,估计就得浪费一个早上去看堆资料,而且行数比反射只多不少.
那种感觉,真的就象柳永的词,卖弄的成分多,实用的成分少.
反射的话,比dyna proxy要更加清楚明确.
不过,话说回来,"酒窝"这东西还是挺可爱的.
samhe
2007-04-02
恩,一直都想搞个类似的东西.哈哈...现在有现成的了.
jellyfish
2007-03-04
java.lang.Number
jianfeng008cn
2007-03-03
又看了一下,感觉对一个程序员来讲,楼主的思路应该很容易理解才合理,至于涉及架构什么的,一般人我还用不到它。
具体实现代码不写了,熟悉beanutils的都知道,不熟悉的,看看javadoc也就明白了(顺便说一下,不要什么东西都动不动就用“学习”两个字,beanutils也要学?pico也要学?是不是街角新开的洗脚房也需要“学习”一下?很让我有一种一个带着眼睛的三十岁左右男子,在和小姐commit transaction之前,在屋里到处细细索索地翻找东西,小姐问说你找什么?回答说要找到manual或者tutorial或者21天精通之类的书先“学习”一下的联想。)
说得过分了,庸俗了,看javadoc就不是学习拉
引用
具体实现代码不写了,熟悉beanutils的都知道,不熟悉的,看看javadoc也就明白了(顺便说一下,不要什么东西都动不动就用“学习”两个字,beanutils也要学?pico也要学?是不是街角新开的洗脚房也需要“学习”一下?很让我有一种一个带着眼睛的三十岁左右男子,在和小姐commit transaction之前,在屋里到处细细索索地翻找东西,小姐问说你找什么?回答说要找到manual或者tutorial或者21天精通之类的书先“学习”一下的联想。)
说得过分了,庸俗了,看javadoc就不是学习拉
shiwentao1982
2007-03-03
没用过这种技巧,学习!
dearmite
2007-03-03
method.invoke(arr[i], args)).
Details.class.getMethod(name);
其实思路是差不多的,都是得到你的类的method.
但是第一种可以用类来传递,
第二种就只可以用字符串来传。
为什么大家对第二种感觉不错,
更有人用JS来弄。
而对楼主的第一种思路不感冒呢?
请大家拿出当初学习getMethod的这种精神来重新审视一下楼主的代码。
和自己是否对新生的JAVA写法有些抵触。
我和楼主的感觉是差不多的。
要么使用for .
要么使用代理。
这两种都有一个共性就是可以重构。
Details.class.getMethod(name);
其实思路是差不多的,都是得到你的类的method.
但是第一种可以用类来传递,
第二种就只可以用字符串来传。
为什么大家对第二种感觉不错,
更有人用JS来弄。
而对楼主的第一种思路不感冒呢?
请大家拿出当初学习getMethod的这种精神来重新审视一下楼主的代码。
和自己是否对新生的JAVA写法有些抵触。
我和楼主的感觉是差不多的。
要么使用for .
要么使用代理。
这两种都有一个共性就是可以重构。
dearmite
2007-03-03
dreamstone 写道
看了文章感觉很有意思,作者的思维挺好玩,感觉如果平时应用开发还是不要这样的好,如果是写一些特殊的需求,例如框架什么的还是不错的。另外再提供一个思路:
是用jdk1.6中的script就可以简单实现了,好处是写起来方便,学习成本低。坏处是也存在重构问题,并且内部实现和动态代理、反射等也差不哪里去。
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
dearmite
2007-03-03
ajoo 写道
继续我们的奇技淫巧之旅。
4。于是,就出现了我们慕容家的“斗转星移”神功了。就像做梦一样,你可以这样写了:
作为一个客户,我几乎无法挑出什么毛病了,所有的Eclipse提供的拐棍都还在,语法也是简练自然得象自然语言。
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
并冠以公司财务系统高级架构包一员的美名。
那么研究之人肯定比现在要多。
:)
glassprogrammer
2007-03-02
huhu, 我也赞成第二种方法是标准解耦的方法. 不过奇淫技巧也是很有价值的, 人类历史上的重大发明, 开始的时候还不都是奇淫技巧.
intolong
2007-03-01
第二种方法是标准解耦的方法。其他的才是奇淫技巧
yyzheng
2007-03-01
ajoo 写道
yyzheng 写道
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
理解万岁。呵呵。
不过,这些新的接口是不要求被sum的类来"implements"的。因为“酒窝”可以搞定这一切。所以,不能算“侵入”吧?
哦,我又看了一下,可以不用在原先系统架构的那些废接口上做文章,直接添加新的接口即可,这样的话我说的:“因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构”就不成立了,看来“酒窝”是个好东东,有时间要好好研究一下:)
我这里说的侵入含义比较广,不仅仅是强迫应用类实现某些特定的接口或继承某些特定的基类,当然,按照这种概念,只要你用到了某种框架,其实都或多或少算作“侵入”了。但作为一个框架,提供给应用层的接口很重要,好的框架提供的侵入方式很自然,就像spring。
新增的这些接口仅仅是为了用到重构后的模块而加的,体现的是一种框架的配置语义,这样的话怎么组织这些东东以提供更clean的接口给团队其他程序员就很重要了,不然其他人会抱怨的:)
其实我觉得你提出的第二种Getter接口的方法就已经很好了,面向接口、简单实用、在架构上也很清晰,做应用的话写到这种程度就已经足够了,匿名内部类其实也很简单,而且有编程工具的支持,虽然代码行数没少多少,但从整体架构上更清楚了。
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(),你给这老多
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();
}
}
sandy
2007-03-01
这种情况,还是借助于工具自动生成代码技术。
用什么反射,效率低,代码也很难看
我宁愿重复
用什么反射,效率低,代码也很难看
我宁愿重复
ajoo
2007-03-01
yyzheng 写道
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
理解万岁。呵呵。
不过,这些新的接口是不要求被sum的类来"implements"的。因为“酒窝”可以搞定这一切。所以,不能算“侵入”吧?
yyzheng
2007-02-28
呵呵 我觉得楼主在最后一贴的“疯狂”是有道理的,当整个系统中大部分业务处理逻辑都是“求和”的情况下,用一种方式将这种逻辑抽象出来我认为也不为过,只不过他用的技巧有点让人眼晕,不过没关系,因为一个团队里总会出一两个这样的“疯子”,让这些“疯子”维护这种代码即可,从架构的角度上看,楼主明显已经抽象出一层业务处理逻辑(hoops...难不成这就是传说中的业务平台。。。)
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
从design的方向上,我赞成楼主的观点,但从design的技巧上我不认同楼主的技巧,why:因为楼主利用了原有设计上的一个缺陷,就是对每一个class原来的设计都强迫其实现一个接口,这种取巧的方式不利于以后architecture的重构,而且楼主为了让没有实现接口的class也能用到重构后的模块,又新加了一些特定的接口,而这些接口是没有任何业务语义的,仅仅是为了用到重构后的模块而加的(hoops...难不成这就是传说中的侵入。。。)好吧,如果你想让按楼主重构后的架构clean一些的话(我承认我有一些技术洁癖),又需要再付出努力组织这些东东,而这样的代价是否值得呢,这实际上就是在重新架构你的system。。。
dreamstone
2007-02-27
看了文章感觉很有意思,作者的思维挺好玩,感觉如果平时应用开发还是不要这样的好,如果是写一些特殊的需求,例如框架什么的还是不错的。另外再提供一个思路:
是用jdk1.6中的script就可以简单实现了,好处是写起来方便,学习成本低。坏处是也存在重构问题,并且内部实现和动态代理、反射等也差不哪里去。
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就可以简单实现了,好处是写起来方便,学习成本低。坏处是也存在重构问题,并且内部实现和动态代理、反射等也差不哪里去。
yiding_he
2007-02-27
思路新颖,快刀斩乱麻!
qingjian
2007-02-27
感觉 lz好痛苦
- 浏览: 182186 次

- 详细资料
搜索本博客
最近加入圈子
最新评论
-
SQL 小技巧
第三个问题,先写出代码来吧。等有点时间再解释一下。第四个问题其实可以照猫画虎的: ...
-- by ajoo -
SQL 小技巧
第一个问题是我在维护一个金融分析软件的时候碰到的。原来的那位老兄正儿八经地用一个 ...
-- by ajoo -
SQL 小技巧
效率没问题。实际上一般的query效率都在查询上,至于对查询结果的计算,代价基本 ...
-- by ajoo -
Not Convinced about Java ...
最讨厌所谓的魔法了,调试的时候能让人吐血。
-- by aninfeel -
SQL 小技巧
ajoo 写道Readonly 写道问题一,经过google得到一用sum,lo ...
-- by Readonly






评论排行榜