呃…… 标题不太好。让我在问题描述里解释一下。
让我以 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 的舒畅感觉(划掉)。
那么问题来了:为什么有的人不使用最后一种写法呢,倒反是使用第一种写法呢?
巴扎黑2017-04-17 18:02:20
我也比較贊同第二種寫法,其優勢你也都說到了,就不在贅述。但一直奇怪為什麼Google官方的例子為什麼是用第一種方式寫的,後來在Android的文檔中看到一段話,大致內容是:Java創建對像其實開銷是很大的,如果所有的監聽都創建單獨的對象,這樣會使得程式產生非常多的額外開銷。就是如此Google選擇用同一個物件來完成所有的監聽,這主要是因為早期Android設備性能不是特別好,只能在這種細節上節省性能,而現在設備都已經不在需要太關心這些了,所以用第二種方式也已經無傷大雅。
PHP中文网2017-04-17 18:02:20
我個人比較喜歡最後一種寫法,而且如果 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()
)
怪我咯2017-04-17 18:02:20
composition over inheritance
這個話怎麼理解?組合一定優於繼承?因為繼承的一些不好名聲,導致大家現在看到繼承就先入為主的拒絕繼承。
但是實際上真是如此?我不認同。
在OO中物件關係中大致分為,關聯,依賴,組合,聚合,繼承,泛化等幾種關係,這幾種關係約定了物件之間的關係。
也就是說對象之間關係該是什麼關係就是什麼關係,準確的說絕對沒有組合由於繼承的概念,不知道是因為繼承的易於實現,導致大家都喜歡使用繼承去復用,其實本質上是對於對象抽象的不夠導致。
所以有些人就乾脆提出組合優於繼承說法來簡化大家的思考。
在回到題主的問題,為什麼google會這樣實現?在業務角度來看,一個activity的生命週期大致會處在幾個階段,整個物件的生命週期交給android來實現,但是對於幾個關鍵點透過模板的方式來進行暴露,是一個很合理的設計,不明白題主為什麼會有這樣的想法。
怪我咯2017-04-17 18:02:20
我只在listener很少的時候才寫匿名類,一般都實現接口,如果點擊事件代碼量大,就單獨寫個方法再放在onClick中執行它。我認為這樣程式碼邏輯會比較清晰整潔。
伊谢尔伦2017-04-17 18:02:20
把事件處理抽象化到一個入口處理,再抽象化成一個通用介面。這是類的概念,不具體到業務。
你後面的寫法 明顯是具體到物件概念。
我也是比較習慣這樣寫,反正本來就是具體的業務,具體的對象,也不需要做通用