• 4.3k

为什么很多人写 Java/Android 时,选择让同一个类实现多个接口,而不是用多个内部匿名类?

呃…… 标题不太好。让我在问题描述里解释一下。

让我以 Android 开发中一个简单的例子说明:在一个 Activity 中有多个可点击的按钮时,很多人会这么写:

public class ExampleActivity extends Activity implements OnClickListener {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(this);
        findViewById(R.id.second_button).setOnClickListener(this);
    }

    @Override
    public void onClick(final View v) {
        switch (v.getId()) {
            case R.id.first_button:
                // bla bla bla
                break;
            case R.id.second_button:
                // bra bra bra
        }
    }
    
}

事实上,Android 官方有些 sample 里面也是这么写的。然而在我看来,这么写代码是非常不优雅的,因为一个 OnClickListener 的实现,只应该关注和点击事件本身相关的内容,它的含义和 Activity 的含义是截然无关的,让同一个类继承/实现他们,会使得这个类的含义变得不清晰。同时,这样还给 ExampleActivity 类增加了一个 public 的方法,削弱了这个类的封闭性。

所以如果像下面这样,会好不少:

public class ExampleActivity extends Activity {

    private OnClickListener onClickListener = new OnClickListener() {
        @Override
        public void onClick(final View v) {
            switch (v.getId()) {
                case R.id.first_button:
                    // bla bla bla
                    break;
                case R.id.second_button:
                    // bra bra bra
            }
        }
    };

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(onClickListener);
        findViewById(R.id.second_button).setOnClickListener(onClickListener);
    }
    
}

这样写体现了 composition over inheritance 的思想。它避免了上面的所有问题,看起来舒服得多。

不过,这样还是让阅读代码时很不方便——看到 onCreate 里面时,还不得不经常滚动到声明 onClickListener 的地方去,并且在 onClick 中艰难的寻找真正和某个特定按钮相关的代码。当然这两个问题之前那个版本也都无法避免。

另一件糟糕的事情是,不同按钮的 listener 逻辑很可能是相对独立的,放到同一个 onClickListener 里,还是很丑陋。

所以为了进一步避免这几个问题,我一向都是用下面这样的写法:

public class ExampleActivity extends Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);

        findViewById(R.id.first_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // bla bla bla
            }
        });
        findViewById(R.id.second_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // bra bra bra
            }
        });
    }
    
}

这样的话,不同逻辑间相对独立,看起来非常舒服,便于阅读,并且让我找回了写 JavaScript 的舒畅感觉(划掉)。

那么问题来了:为什么有的人不使用最后一种写法呢,倒反是使用第一种写法呢?

阅读 3.7k
评论
    9 个回答
    • 26.6k

    我也比较赞同第二种写法,其优势你也都说到了,就不在赘述。但是一直奇怪为什么Google官方的例子为什么是按第一种方式写的,后来在Android的文档中看到一段话,大致内容是:Java创建对象其实开销是很大的,如果所有的监听都创建单独的对象,这样会使得程序产生非常多的额外开销。就是如此Google选择用同一个对象来完成所有的监听,这主要是因为早期Android设备性能不是特别好,只能在这种细节上节省性能,而现在设备都已经不在需要太关心这些了,所以用第二种方式也已经无伤大雅。

      • 41.3k

      我个人比较喜欢最后一种写法,而且如果 OnClickListener 要做的事情特别多,我可能还会单独写成命名类。

      不过确实有很多第一种写法,我猜原因一个是示例误导,因为示例代码通常比较少,这样写也没什么问题,代码也不会很长,但实际业务的话,代码可能要多得多;另一个原因可能是有人觉得写类太重量级了,当然实际上写类并不一定就比写方法重量级,这是有理论和实际依据的,有人做过分析,我就不多说了。

      在 C# 中,是采用委托的实现不同 button 的 Click 事件,而每个委托对应于当前 Form(如果是 WinForm 程序的话,ASP.NET 应用类似)的某一个 XxxxButton_Click 方法,这种写法是从古老的 VB/VC 遗传下来的,当然也不能排除UI Designer 要这么干的事实。由于 Lambda 的普及,也越来越多的人开始转用类似楼主的第三种写法,只不过实现的不是一个接口,仅仅是个 Lambda——而且还有个前提,在不使用 UI Designer 生成代码的情况下。

      为什么说到 C#,因为 Java 已经意识到了 Lambda 的优越,所以也实现了 Lambda,以单方法接口的形式模拟了 C# 的委托,那么楼主第3种写法还可以更简捷一些。

      不过任何事务都有两面性,Lambda 提供方便的同时也带来一些不便,尤其是逻辑复杂的时候。所以对于逻辑比较复杂的事件处理,我仍然建议写单独的处理类,即可以是一个事件接口的实现(比如 OnClickListener 的实现),也可以是一个独立的(或一堆相关的)业务处理类,在 onClick 中一句话调用(比如 new Business(params).Go()

        composition over inheritance
        这个话怎么理解?组合一定优于继承?因为继承的一些不好名声,导致大家现在看到继承就先入为主的拒绝继承。
        但是实际真是如此?我不认同。
        在OO中对象关系中大致分为,关联,依赖,组合,聚合,继承,泛化等几种关系,这几种关系约定了对象之间的关系。
        也就是说对象之间关系该是什么关系就是什么关系,准确的说绝对没有组合由于继承的概念,不知道是因为继承的易于实现,导致大家都喜欢使用继承去复用,其实本质上是对于对象抽象的不够导致。
        所以有些人就干脆提出组合优于继承说法来简化大家的思考。

        在回到题主的问题,为什么google会这样实现?在业务角度看,一个activity的生命周期大致会处在几个阶段,整个对象的生命周期交给android来实现,但是对于几个关键点通过模板的方式来进行暴露,是一个很合理的设计,不明白题主为什么会有这样的想法。

          这就是老外对程序开销的深刻理解。已经深入骨髓了。

            我一直用第二种写法,刚开始学Android的时候是第三种,但是listener内容多的话,看起来也不是太好

              我只在listener很少的时候才写匿名类,一般都实现接口,如果点击事件代码量大,就单独写个方法再放在onClick中执行它。我认为这样代码逻辑会比较清晰整洁。

                把事件处理抽象到一个入口处理,再抽象成一个通用接口。这是类的概念,不具体到业务。
                你后面的写法 明显是具体到对象概念。

                我也是比较习惯这样写,反正本来就是具体的业务,具体的对象,也不需要做通用
                图片描述

                  • 4
                  • 新人请关照

                  用一次的对象,直接用匿名!

                    无非就是内部类会持有外部类的实例,容易造成内存泄漏
                    所以就性能方面 第一种写法优于第二种写法,第二种写法优于第三种写法

                      撰写回答

                      登录后参与交流、获取后续更新提醒

                      相似问题
                      推荐文章