为什么很多人写 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 的舒畅感觉(划掉)。
那么问题来了:为什么有的人不使用最后一种写法呢,倒反是使用第一种写法呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我也比较赞同第二种写法,其优势你也都说到了,就不在赘述。但是一直奇怪为什么Google官方的例子为什么是按第一种方式写的,后来在Android的文档中看到一段话,大致内容是:Java创建对象其实开销是很大的,如果所有的监听都创建单独的对象,这样会使得程序产生非常多的额外开销。就是如此Google选择用同一个对象来完成所有的监听,这主要是因为早期Android设备性能不是特别好,只能在这种细节上节省性能,而现在设备都已经不在需要太关心这些了,所以用第二种方式也已经无伤大雅。
我个人比较喜欢最后一种写法,而且如果
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中执行它。我认为这样代码逻辑会比较清晰整洁。
把事件处理抽象到一个入口处理,再抽象成一个通用接口。这是类的概念,不具体到业务。
你后面的写法 明显是具体到对象概念。
我也是比较习惯这样写,反正本来就是具体的业务,具体的对象,也不需要做通用
用一次的对象,直接用匿名!
无非就是内部类会持有外部类的实例,容易造成内存泄漏
所以就性能方面 第一种写法优于第二种写法,第二种写法优于第三种写法