treenode

在路上。

BlogJava 首页 新随笔 联系 聚合 管理
  5 Posts :: 1 Stories :: 53 Comments :: 0 Trackbacks
请比较一下这两段功能大致相同的代码。抛开语法的差别不论,你觉得哪种风格好?你愿意维护哪一段代码?

        b.addSelectionListener( new SelectionAdapter()
        {
            
public void widgetSelected( SelectionEvent e )
            {
                Runnable longJob 
= new Runnable()
                {
                    
boolean    done    = false;
                    
int        id;

                    
public void run()
                    {
                        Thread thread 
= new Thread( new Runnable()
                        {
                            
public void run()
                            {
                                id 
= nextId[0]++;
                                display.syncExec( 
new Runnable()
                                {
                                    
public void run()
                                    {
                                        
if ( text.isDisposed() )
                                            
return;
                                        text
                                                .append( 
"\nStart long running task "
                                                        
+ id );
                                    }
                                } );
                                
for ( int i = 0; i < 100000; i++ )
                                {
                                    
if ( display.isDisposed() )
                                        
return;
                                    System.out
                                            .println( 
"do task that takes a long time in a separate thread "
                                                    
+ id );
                                }
                                
if ( display.isDisposed() )
                                    
return;
                                display.syncExec( 
new Runnable()
                                {
                                    
public void run()
                                    {
                                        
if ( text.isDisposed() )
                                            
return;
                                        text
                                                .append( 
"\nCompleted long running task "
                                                        
+ id );
                                    }
                                } );
                                done 
= true;
                                display.wake();
                            }
                        } );
                        thread.start();
                        
while ( !done && !shell.isDisposed() )
                        {
                            
if ( !display.readAndDispatch() )
                                display.sleep();
                        }
                    }
                };
                BusyIndicator.showWhile( display, longJob );
            }
        } );
另外一种:
        delegate void NotifyStartDelegate( int threadId );
        
delegate void NotifyFinishDelegate( int threadId );
        
        btnInvoke.Click 
+= BtnInvokeClick;
        
        
void BtnInvokeClick(object sender, System.EventArgs e)
        {
            text.Text 
= "invoke long running job";
            
            Cursor 
= Cursors.WaitCursor;
            Thread thread 
= new Thread( new ThreadStart(ThreadProc) );
            thread.Start();
        }
        
        
private void ThreadProc()
        {
            
int threadId = nextId ++;
            
bool done = false;
            
            
if ( IsDisposed )
                
return;
            
            Invoke( 
new NotifyStartDelegate(notifyThreadStart), new object[] { threadId } );
            
for ( int i=0; i<100000; i++ )
            {
                
if ( IsDisposed )
                    
return;
                Console.WriteLine( 
"do task that takes a long time in a separate thread " + threadId );
            }
        
            
if ( IsDisposed )
                
return;
            Invoke( 
new NotifyFinishDelegate(notifyThreadFinish), new object[] { threadId } );
            done 
= true;
        }
        
        
private void notifyThreadStart( int threadId )
        {
            text.Text 
+= "\r\nStart long task " + threadId;
            threadCount 
++;
        }
        
        
private void notifyThreadFinish( int threadId )
        {
            text.Text 
+= "\r\nCompleted long running task " + threadId;    
            threadCount 
--;
            
if ( threadCount == 0 )
                Cursor 
= Cursors.Default;
        }
        
        
private int nextId = 0;
        
private int threadCount = 0;

我在另一个地方也抱怨过:在所有我了解的语言特性里面,没有一种像Java内部类一样让我觉得反感——甚至到了恶心的地步。大多作B/S系统的Java程 序员可能不会有这样的感觉,因为那个领域基本上很少会用到这个概念。可是在C/S,不管用Swing还是SWT,内部类都是绕不过去的一座大山。在阅读Eclipse站点上许多代码示例以后,我终于有了痛苦到——一点也不夸张——想要作呕的地步。上面第一段代码就是让我感到窝心的代码之一(仅仅是其中之一,还不是最丑陋的)。我想,Java 语言的发明者大概从来就没写过桌面程序;他根本也不打算为这个领域的程序员提供一种比较好的事件回调机制。

内部类有什么问题呢?首先,你愿意在去看代码逻辑之前,先花上好几分钟去搞清楚“这个括号到底是和哪个配对”这种蠢问题吗?你不妨回头看看第一段的代码,想想这段其实相当简单的程序,是不是真的值得用这么多括号去考验你的智力。

内部类是对封装的严重破坏。它对外部类的任何私有变量都有完全的访问权限——如果你突然发现某个变量的内容不对劲了,你不能仅仅在setXXX里面加个断 点就指望能捕获到错误;真正的元凶可能是内部类里面的哪一句呢。如果内部类都非常简单,那倒也没什么。可是当内部类用来实现事件的时候,你就没法指望它一 直那么简单了。

内部类是测试的盲区。TDD总是说,要测试,测试,所有包含逻辑的类都应当通过充分的测试。可是内部类怎么测试?只要想想就能知道,大多数内部类是根本没法测试的,它和外部类实在是耦合的太紧密了。匿名内部类的问题更严重——它是绝对无法测试的。你怎么测试一个连名字都没有的方法?

不管有意无意,内部类在(至少我看到的)实践中事实上鼓励了不好的编程风格。就是说,它违背了DRY(Don't Repeat Yourself)的原则。比如,button.addSelectionListener后面几乎总是跟着SelectionAdapter+括号+ widgetSelected原型再+一堆括号;Display.asyncExec后面总是要写上new Runnble(),void run(),括号,等等。千篇一律的东西,可是又不得不写。而且,几乎没有什么好的办法可以改进!因为语法的规则要求你必须这样做。每天写这些无聊的东 西,你的话会不会烦?哦,工具是有的。可是工具只负责生成代码,以后的维护还是要你来做——不是么?
posted on 2006-06-15 22:37 TreeNode 阅读(3285) 评论(31)  编辑  收藏 所属分类: Java技术

Feedback

# re: 内部类让我厌恶Java 2006-06-15 22:44 Anders小明
嘿嘿,java语言有些地方是不如.net做的好!另外java的语言的演化在有些地方背离了它的初衷。.net平台一开始规划的比较好!  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-15 23:07 CowNew开源团队
其实你完全可以这样写,只把内部类需要实现的方法中抽取出一个新的方法来,比如:
b.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected( SelectionEvent e )
{
on_widgetSelected(e);
}
} );



private on_widgetSelected(SelectionEvent e)
{
Runnable longJob = new Runnable()
。。。。。。。
}
on_widgetSelected中的方法还可以进一步如此抽取。java确实不适合做界面开发这种层次的工作(虽然sun在努力改进这一点)。如果用好了,java的内部类可比.net的delegate 好用多了,内部类可不光光只是用来做回调这么简单的工作的。  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-15 23:39 江南白衣
JFunctor这种用反射实现函数指针的方法可以用么?  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-15 23:46 TreeNode
答CowNew:我自己就经常这么写。不过这样也没多少好处,比比看C#怎么写:
btnInvoke.Click += BtnInvokeClick;

再看看Java如何用四五行代码、两层括号实现同样的功能。何苦呢?

我倒是很想看看什么地方可以用内部类优雅的解决其他办法解决不了或很难解决的问题。但是我还没有看到过。倒是很多Java教科书都苦口婆心的说:学会适应内部类吧,熟悉以后你会发现它并不是那么难。这样的说明本身就让我觉得很有趣。

回调简单吗?我在这个BLOG上的另外一篇里面也说到这个问题。回调概念简单,实现起来其实是相当复杂多变的。这个领域里C++有functor,MFC有Handle Map,ATL有Thunk,VCL有TMethod,Python和Ruby有closure。多到让人眼花缭乱的地步。Java的实现是最让我觉得难看的一种。

答江南白衣:
JFunctor我不了解,有机会看看。
自己曾想过用反射,不过反射的问题是Java并不把函数当成对象,因此C#那样的语法是行不通的。如果用方法名称的话就没有编译器检查,同步是个问题。
另反射的性能也要考虑,对于一般的消息没有问题,如果是MouseMove或者很频繁的Timer事件是会影响效率的。  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 00:14 CowNew开源团队
我对.net的delegate 理解并不是很深入,可能说的有错误。我认为delegate只是一个方法指针而已,而java的内部类则是一个类,虽然可能有的时候会和宿主类共享一些成员,但是其封装性和可复用性更强。  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 00:34 TreeNode
delegate事实上不是指针而是对象,一个派生于MulticaseDelegate的对象。M$隐藏了它的细节而已。

内部类的封装和复用体现在哪呢?我是这么看的:如果它和外部类需要如此紧密的耦合,以至于可以完全访问外部类的所有私有字段,那么它恐怕根本就不应该作为一个类。不然的话,它反而只是在破坏外部类的封装。如果它和外部类没有耦合或者只有接口耦合,那么它完全应当拿出来作为单独的类。

我不知道内部类复用性强指的是什么。内部类严重依赖于外部类的存在,你根本没法把它单独拿出来复用。也许你说的是多个方法可以共用同一个内部类?这不是什么值得一提的优点,其他语言的回调方法一样是可以共用的。

我还是希望能看到一个能证明内部类优点的实例,空对空的感觉实在是不太好。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 00:58 绿色使者、绿色心情
@TreeNode
同意这种说法
.net很多语法概念,相比java来说,都更便利、高效
原来一直专注于.net,其中的attribute、delegate,property等等的用法都很喜欢的
delegate和event配合起来,还可以实现观察者设计模式,在很多用到事件、消息交换等等的逻辑中,显得非常直观
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 09:52 Robin's Java World
我想说的是,首先你不应该这么讨厌内部类,作为一种语法存在,肯定有一定的道理,而在编程中,我们也确实由于内部类而获得了一些方便。

或许你是从.net到java,所以有一些.net先入为主的感觉,你的思维有些习惯.net的做法。我一开始就做java,在做VB和VC编程时反而有些不习惯,思维上有很多方式调整不过来,有些问题自然而然的就想到了用java的思维方式来解决问题。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 10:10 原创专栏 开源学习
不要眼中只有java,c#.
内部类可以模拟闭包的一些特性。

那估计你不会接受 python,ruby,javascript这些灵活的语言了。

.net的delegate只是对MVC的一种封装,自己实现了个万能的监听器,又做了优化。
代码写起来是好了,可理解上就比较差了。 偶是看了 李建忠 翻译的那本书才弄明白的。其实.net也有很多问题,为了让开发人员方面,丢掉很多面向对象的概念,对有些人,比如我来说,觉得不伦不类。

http://www.dearbook.com.cn/book/12797
这本书不错  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 13:29 google
内部类有时候很不错哟  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 13:40 puke
既然恶心到这个程度就不要用了,blogjava也清静点  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 15:29 steeven
彼此彼此,avalon里面的静态***Property也很恶心嘛,一堆一堆的.

毕竟java出来的早,还在恐龙年代,别指望和现代美女比较啦.
sun也不争气,赶快被google收购算球,浪费java  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 18:41 CowNew开源团队
内部类不只是用来做回调,回调也不只能用内部类来实现,用内部类实现的回调如果设计合理的话也不一定会ugly。只能说java中因为没有delegate所以在事件监听实现上只能用接口实现,而很多人用的时候是直接写了实现接口的内部类,而没有进行必要的抽象。内部类没有错,只是有人把它用错了。  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 19:29 TreeNode
还是没看到一个实例。呵呵。

@绿色使者、绿色心情
基本同意你的看法。Anders做了那么多年Delphi的架构,对于语言的理解的确很少有人能超过他。更重要的是他是一个注重实际的人,了解这方面程序员的实际要求。不过在企业架构这方面他也并不怎么在行,人无完人吧


@Robin's Java World:
你猜错了,我的背景是VC和Delphi。接触.Net要比Java晚两三年。
我自认对语言没什么偏见,Java的语言特性绝大多数也都乐意接受。只有内部类是例外。

@原创专栏 开源学习:
估计错误。我很喜欢Ruby,我觉得Ruby用Block来实现回调的办法很灵巧。

理解性差何指?如果不研究实现细节,语法上没什么不好理解的。
.Net面向对象性问题,我认为ADO.NET和ASP.NET的总体设计是有缺陷的。
但是对delegate这个语法特性,我觉得没什么问题。


@puke:
内部类我能不用就不用,但是Java我还是要用的。OTL

@CowNew:
如果大家都在用看上去有问题的办法,那就一定存在真正的问题。为什么那些人都要用内部类呢?因为没有办法。难道有多少个菜单/按钮你就写多少个外部类不成?如果说有人用错了,那么Java也有责任,因为这种语法就是在逼着人用错误的办法做事。


另:在google上搜索jfunctor竟然只有几个看上去像C++的东西,关键字错误吗?  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 19:55 江南白衣
不好意思我完全写错单词了,应该是FunctionalJ(http://functionalJ.sf.net)  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 20:12 寒寒
哈哈,继续  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-16 20:22 TreeNode
看了,FunctionalJ还是弱类型的,引用方法名用的是字符串。
这种办法还是需要得到工具的支持才用得起来。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-20 11:46 霉干菜
同意 CowNew说的
b.addSelectionListener( new SelectionAdapter()
{
public void widgetSelected( SelectionEvent e )
{
on_widgetSelected(e);
}
} );

多数用倒内部类的时候都是这种swing写法,就在listener里面新声明一个
内部类就是了,不用写到外面得,多看看就习惯了,哪种语言就算再烂用的
多了以后看的都会习惯得  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-22 04:17
匿名类确实恶心,恶心的要命. 什么还没写呢,先弄四五行没用的垃圾在代码里。

不过,和什么“可测试”啊,“封装”啊就别往一块儿拉扯了。除了语法难看之外,它和其它任何一种closure技术概念上都一样。想要单独测试那块代码,你不会单独做个命名外部类先?匿名类本来就是为了封装一些和局部上下文紧密相关的比较简单trivial的逻辑的。


functionalj这种东西我不看好。除了一个简单的reflection to abstract class的转换器 (差不多的程序员都可以花半个小时自己写一个的),难用的curry机制,简陋的几个初级函数式算法,啥都没了。

要找java的delegate实现,google一下,有比它更好的。
要在java里搞函数编程,思想可以借鉴,但是这么邯郸学步不成的。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-22 09:54 fantasy
内部类让我厌恶 ? .... 匿名类好象才怎么不好玩~

不过看Java集合类的 Iterator 模式的实现....

内部类还是很好的。   回复  更多评论
  

# re: 内部类让我厌恶Java 2006-06-22 18:59 TreeNode
@猪:
我不知道你有没有用过Swing或SWT。为什么我讨厌内部类还不得不用它?原因文章和回复里已经说过好几次了。

我还特意把程序代码摆了出来,似乎很多人都不看。哪位有信心说“我能把第一段代码改到很漂亮”吗?那样我就相信你。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-07-06 21:50 auguusstt
楼上太夸张了八,你把java的写成那样,再把c#的粉饰一遍,这是不是太哲学脑瓜了!
随便挥挥手重构2分钟都不会让人有这样的错觉!!!
因为java中没有把函数作为一等公民,所以确实没有C#的简洁,但还不至于像陀屎,在很多情况下也还是很漂漂的。当然加上c#的语法特性会让有些情况简单下来

class MyRunnable3 implements Runnable{
public void run() {
if ( text.isDisposed() ) return;
text.append( "\nCompleted long running task " + id );
}
}

class MyRunnable2 implements Runnable{
public void run() {
if ( text.isDisposed() )
return;
text.append( "\nStart long running task "+ id );
}
}

class MyRunnable implements Runnable{
public void run(){
id = nextId[0]++;
display.syncExec(new MyRunnable2());
for ( int i = 0; i < 100000; i++ ){
if ( display.isDisposed() )
return;
System.out.println( "do task that takes a long time in a separate thread " + id );
}
if ( display.isDisposed() )
return;

display.syncExec(new MyRunnable3());
done = true;
display.wake();
}
}

class MySelectionAdapter extends SelectionAdapter implements Runnable {
public void widgetSelected( SelectionEvent e ){
BusyIndicator.showWhile( display, this );
}
public void run() {
Thread thread = new Thread( new MyRunnable());
thread.start();
while ( !done && !shell.isDisposed() )
{
if ( !display.readAndDispatch() )
display.sleep();
}
}
}

b.addSelectionListener( new MySelectionAdapter ());
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-07-07 08:43 TreeNode
楼上:这段代码不是我写的。我不过是把Eclipse.org上面那些大牛的code摘下来一段。

你看看你重构出来的这几个类有什么特点?它们都只有一个方法,并且没有自己的任何状态。这是一个典型的反模式。一个没有自己状态的类根本不应当是一个类。就像Math一样,不过是为了为那些没主的方法找一个杂货柜。内部类在这里就像一个杂货柜。

更新text文本本来就应当是窗口的责任。把它作为类的方法是最自然最清晰的,而且可以重用。硬是塞一个内部类干什么?为了满足方法签名而已。除此以外对代码结构没有一点好处。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-07-07 15:28 auguusstt
这是java没有函数为第一公民的功能缺少,而不是匿名类或内部类的问题
有一天您上茅厕,上完后,发现茅厕并没有准备厕纸,没办法,发现每个隔档都有一个门帘,得救,但由于门帘质地太硬,把屁股擦烂了,于是大骂门帘过于粗糙。  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-07-07 21:36 TreeNode
Java和内部类都不是我骂的对象。语法只是语法,语法是无辜的。我想骂的是这个设计语言的人。
  回复  更多评论
  

# re: 内部类让我厌恶Java 2006-12-01 15:57 心内求法
@TreeNode
骂也没用啊,默默承受吧,默默享受吧,呵呵  回复  更多评论
  

# re: 内部类让我厌恶Java 2007-03-31 17:57 喜来乐哈哈
@TreeNode
没有万能的语言。

记得一位大牛说过,"设计一门语言最难的一个问题就是对一些优秀的概念说不。" Java没有加入delegate肯定和当时Java团队的设计目的有关,别忘了Java最初只是作为小电器的控制语言而设计的,俗话说,三岁看老。都是有历史原因的。怎样说Java比C#对编程语言的贡献要大很多。有人采访C++的创始人Bjarne Stroup的时候问他对C#的意见,Bjarne Stroup说,他不知道为什么还要开发一门新的像C#这样的语言。

匿名类和内部类是在Java1.1才增加的,当时很多Java程序员也反感这东西,主要是不喜欢的它的句法。在此之前的Java程序员往往是这么写的

class Clazz implements SelectionAdapter {
public void widgetSelected(SelectionEvent e){
// 你的实现
}

Clazz() {
b.addSelectionListener(this);
}
}

这样写, 一个不好的地方就是, Clazz也是一个SelectionAdapter.

应该说当我们习惯内部类以后, 内部类还是给我们带来了一定的便利. 还是一句老话, 天下没有免费的午餐. 凡事都是有代价的.

内部类相比delegate的长处还是有的, 比如, 内部类也可以继承自另一个类, 从而继承了另一个类的实现. 这样的例子比比皆是, 我想, delegate没法做到吧.

当然喜欢delegate的人不只你一个, 德国的一个教授搞过一个项目, 好像叫Darwin Lava, 就尝试为Java加上delegate.

Java也有可能在将来加入delegate.

顺带说一句, IBM的大牛们写的Java代码, 从细了讲谈不上漂亮, 那帮人大多原来都是用Smalltalk的, 但是大的结构和框架非常漂亮. 里面有太多软件设计的大牛了.

其实从编程语言本身来说Smalltalk要比Java优秀, 而且出现的时间也差不多, Java成功了, 使用Smalltalk的人却越来越少. 这个世界很有意思的啦.

  回复  更多评论
  

# re: 内部类让我厌恶Java 2007-03-31 18:10 喜来乐哈哈
@auguusstt
您的厕所和门帘的比喻非常形象. 问题是,

下次该换个厕所呢, 还是继续用门帘呢?
厕所该不该提供厕纸?

纯属搞笑, 把内部类比喻成门帘有点冤枉.  回复  更多评论
  

# re: 内部类让我厌恶Java 2010-10-18 18:13 anonymous
@TreeNode
看到你给的这段代码, 怀疑这位 eclipse 中的大牛的水准, 最少是可读性代码 coding 的水准  回复  更多评论
  

# re: 内部类让我厌恶Java 2013-09-10 15:35 yicone
@auguusstt
要骂肯定也是骂厕所居然不提供厕纸,骂门帘的心理不健全 /tx  回复  更多评论
  

# re: 内部类让我厌恶Java 2013-09-10 15:56 yicone
@喜来乐哈哈
很客观了。
另外,lz客观地痛陈使用内部类的缺点,对于已经很客观看待该问题的人可能没什么帮助,但是多数人不见得有那个见识,所以还是有好处的,比如参与讨论的多数人,仍然只是空空地来一句“还是有好处的”。

“ 这个世界很有意思的啦”,赞。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: