Android開發中,匿名類的應用使得開發更加靈活,而匿名類的濫用卻讓程式變得雜亂無章、難以維護。程式設計師都是喜歡偷懶的,我們常常看到一個Activity類中佈滿了匿名類,有Thread、有Handler、有Adapter、有各種Listener以及給匿名類傳遞資料的final變數,給人的感覺就像隨地大小便,很不文明。
Java畢竟不是Javascript,沒有閉包和沒有匿名函式,把物件導向設計的語言用函數語言程式設計的思想來寫是很牽強的。下面貼一段JS程式碼:
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) { if(err) throw err; db.dropDatabase(function(err, done) { db.createCollection('test_custom_key', function(err, collection) { collection.insert({'a':1}, function(err, docs) { collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}).toArray(function(err, items) { console.dir(items); // Let's close the db db.close(); }); }); }); }); });
得益於JS的閉包、匿名函式、函式即變數等特性,JS開發者可以將函式巢狀發揮到令人歎為觀止的地步,而且即使套了很多層,也不會顯得很臃腫。你能想想如果Java套這麼多層會是什麼效果嗎?或許編輯器都顯示不下最長的縮排了,但能多Android開發者就好這口,來看個例子:
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((ListView) findViewById(R.id.listview)).setAdapter(new BaseAdapter() { @Override public View getView(int arg0, View arg1, ViewGroup arg2) { if (arg1 == null) { arg1 = LayoutInflater.from(MyActivity.this).inflate( R.id.feed, arg2, false); } arg1.findViewById(R.id.subject).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { final Handler handler = new Handler( new Callback() { @Override public boolean handleMessage( Message msg) { // 此處省略50行 return false; } }); new Thread(new Runnable() { @Override public void run() { // 此處省略100行 handler.sendEmptyMessage(0); } }).start(); } }); return arg1; } // 。。。。。。 }); } }
這是個真實的例子,其中很多習慣很多安卓開發者也有。這個onCreate函式竟然有近千行程式碼,已經完全喪失可讀性了。如果你走了有人來接替你的活兒,看到這樣的程式碼他一定會把你罵死。
下面我就Android開發中常見的匿名類給出自己的替代方案,僅供參考,錯誤之處還請指證。
用繼承介面代替匿名類
這種方式適合各種Listener,這裡以OnClickListener為例作說明。繫結事件一般是在onCreate時完成的,所以在onCreate中可能會有很多個OnClickListener的匿名類,比如:
findViewById(R.id.button).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // .... } });
這時onCreate方法就會變得冗長,而且包含很多不應該出現在onCreate中的處理邏輯,如果我們讓Activity去實現OnClickListener,將所有點選處理的listener都設為這個activity,在onClick中再根據view.getId()來分支處理,程式的結構就會緊湊很多:
public class DummyActivity extends Activity implements OnClickListener { //..... @Override protected void onCreate(Bundle savedInstanceState) { for(int id : in new int[] { R.id.button }) { findViewById(id).setOnClickListener(this); } } @Override public void onClick(View arg0) { switch(arg0.getId()) { case R.id.button: // ...... break; } } }
包括onLongClickListener等針對單一View的Listener都可以通過讓Activity繼承介面的方式來操作。如果是針對列表項的ItemListener,建議讓Adapter去繼承介面。
用子類代替匿名類
如果一個Activity中有AdapterView的子類,如ListView、GridView,就會設定相應的Adapter,除了最簡單的情形可以用SimpleAdapter、ArrayAdapter直接對映,很多情況都都需要繼承BaseAdapter之類後重寫一些方法,邏輯比較簡單時用內部類不失為可取辦法。例如很多人喜歡在onCreate中這麼寫:
((ListView)findViewById(R.id.listView)).setAdapter(new BaseAdapter() { @Override public View getView(int arg0, View arg1, ViewGroup arg2) { //...... } //...... });
為了不在onCreate參雜不相關的邏輯,保持程式碼結構合理,你可以嘗試這樣處理:
public class DummyActivity extends Activity { //..... @Override protected void onCreate(Bundle savedInstanceState) { ((ListView)findViewById(R.id.listView)).setAdapter(new MyAdapter(....)); } private static class MyAdapter extends Base Adapter{ MyAdapter(...) { ....... } @Override public View getView(int arg0, View arg1, ViewGroup arg2) { ......... } } }
至於static與否則取決於你是否要用到動態成員變數了,這種方式使得引數傳遞也更加方便:只需通過建構函式傳入,不必通過final關鍵字。
用外部類代替匿名類
對於Adapter這類邏輯簡單的類可以用內部類處理,如果涉及到更加複雜的邏輯,比如資料處理、I/O、資料庫操作等,建議用外部類使各個類的業務屬性儘可能單一。比如Thread類,跟Activity類相關度本來就很低,為了偷懶非得硬生生塞進Activity中顯然是不合適的,無論是匿名類還是子類的形式,這裡就不舉具體例子了。至於外部類的命名,如果想不到合適的名字,可考慮字首命名,比如現在有一個DummyActivity,之前在裡面塞了一個MyThread,那提出來再叫MyThread顯然不合適,就以字首命名的方式改為DummyActivityThread就一目瞭然了。
小結
Javascript是門很優秀的語言,擁有函數語言程式設計的諸多特性,合理的靈活巢狀可以使程式碼緊湊而優雅,而Java是一門嚴格物件導向的語言,匿名類巢狀的濫用會讓你的程式臃腫混亂,建議不要把Java寫成了Javascript的風格,那樣你會很難維護。