在 Android 系統中,回撥幾乎隨處可見,最常見的就是給點選事件設定監聽,如下:
1 2 3 4 5 6 |
btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // TODO log in } }); |
如果你開發過 Android 應用,相信上面的程式碼你不會陌生。下面就根據我的理解談一談啥是回撥,為啥使用回撥,以及如何在自己的程式碼中定義回撥。
0x00. 什麼是回撥?
我們先來看一下百度百科中對回撥的定義:
回撥是一種雙向呼叫模式,也就是說,被呼叫方在介面被呼叫時也會呼叫對方的介面。
看不懂?沒關係,我們再來看下面這段話:
在物件導向的語言中,回撥是通過介面或抽象類來實現的,我們把實現這種介面的類稱為回撥類,回撥類的物件成為回撥物件。
我們來把上面兩句話歸結一下:
回撥就是抽象類(或介面)的例項實現父類的抽象方法後,將該方法交還給父類呼叫。
0x01. 為啥使用回撥?
誠如定義中所言:“回撥是一種模式”,既然是模式,肯定是為了解決某一類問題而出現的。那麼回撥解決了什麼問題呢?
這裡我們引用一下劉濟華老師《漫談設計模式》一書中買車票回家過節的例子:
小明準備坐火車回家:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class XiaoMing { public void celebrateSpringFestival() { // Buy ticket buyTicket(); // Travelling by train travellingByTrain(); // Celebrating Chinese New Year celebrating(); } private void celebrating() {} private void travellingByTrain() {} private void buyTicket() {} } |
而小紅離家比較近,她想坐大巴回去,也好辦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class XiaoHong { public void celebrateSpringFestival() { // Buy ticket buyTicket(); // Travelling by bus travellingByBus(); // Celebrating Chinese New Year celebrating(); } private void celebrating() {} private void travellingByBus() {} private void buyTicket() {} } |
細心的你可以發現,兩段程式碼中有相當一部分重複。對於重複程式碼我們如何去優化呢?是的,使用繼承大法。但是,每個人的乘車方式都是不同的,我們無法在基類中作出明確定義,而乘車又是回家過節必需的過程(不考慮離家近的同學步行回家的情況~)。我們只有把乘車方法定義為抽象方法,讓各實現類自行決定如何乘車:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public abstract class BeiPiao { public void celebrateSpringFestival() { // Buy ticket buyTicket(); // Travelling by bus travelling(); // Celebrating Chinese New Year celebrating(); } abstract void travelling(); private void celebrating() { System.out.println("celebrating...") } private void buyTicket() { System.out.println("buy ticket...") } } |
1 2 3 4 5 6 |
public class XiaoMing extends BeiPiao { @Override void travelling() { System.out.println("travelling by train"); } } |
1 2 3 4 5 6 |
public class XiaoHong extends BeiPiao { @Override void travelling() { System.out.println("travelling by bus"); } } |
這時,公司小祕要統計各位同事怎麼回家:
1 2 3 4 5 |
public class Secretary { public void howToGetBack(BeiPiao piao) { piao.celebrateSpringFestival(); } } |
1 2 3 4 5 6 7 8 9 10 |
public class Main { public static void main(String[] args) { Secretary secretary = new Secretary(); secretary.howToGetBack(new XiaoHong()); secretary.howToGetBack(new XiaoMing()); } } // 輸出 travelling by bus travelling by train |
然而公司有位新來的同事,小祕叫不出名字,這咋辦?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Main { public static void main(String[] args) { Secretary secretary = new Secretary(); secretary.howToGetBack(new XiaoHong()); secretary.howToGetBack(new XiaoMing()); secretary.howToGetBack(new BeiPiao() { @Override void travelling() { System.out.print("travelling by taxi"); } }); } } // 輸出 travelling by bus travelling by train travelling by taxi |
哈哈,是不是有點熟悉的感覺了?BeiPiao
類中的travelling()
就是一個回撥函式。
其實我們這裡還順帶講了一個設計模式——模板方法模式,父類先定義好模板,具體細節交給子類去實現。
講了這麼多,相信你應該對回撥有了近一步的瞭解,下個小結我們會講如何在自己的程式碼中定義回撥函式。
0x02. 如何在自己的程式碼中定義回撥?
背景是這樣的:最近在看郭霖大神的《第一行程式碼》,想系統的從頭梳理一遍 Android 的基礎知識。想著自己做一個 Demo 應用,定義一個 ListView,每個條目對應一個知識點。由於每個條目響應的動作都不同,而我又不想在onItemClickListener()
方法中新增無限多個 switch-case 判斷。於是我就自定義了一個Func
類,該類代表著一個功能點:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Func { private OnClickListener onClickListener; private String name; public Func(String name, OnClickListener onClickListener) { this.name = name; this.onClickListener = onClickListener; } public String getName() { return name; } public void onClick() { onClickListener.action(); } public interface OnClickListener { void action(); } } |
自定義介面 OnClickListener
中的action()
就是一個回撥函式。
Adapter 類中的getView()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Override public View getView(final int position, View convertView, ViewGroup parent) { Button btn_show_func; if (convertView != null) { btn_show_func = (Button) convertView.getTag(); } else { convertView = View.inflate(context, R.layout.list_func, null); btn_show_func = (Button) convertView.findViewById(R.id.btn_show_func); convertView.setTag(btn_show_func); } btn_show_func.setText(funcs.get(position).getName()); btn_show_func.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // funcs 是包含多個 Func 類物件的 List 集合 funcs.get(position).onClick(); } }); return convertView; } |
我們可以這樣新增一個條目:
1 2 3 4 5 6 7 8 |
Func force_offline = new Func(getString(R.string.force_offline), new Func.OnClickListener() { @Override public void action() { sendBroadcast(new Intent("com.lovexiaov.learnfromzero.ACTION_FORCE_OFFLINE")); } }); funcs.add(force_offline); |
Talk is cheap, show me the code!
附上GitHub程式碼地址: LearnFromZero
0x03. 小結
合理利用回撥可以使程式碼易於維護和閱讀,就像上面的程式碼,只需要修改一個地方就可以完成新增 ListView Item 的功能。快動手實現屬於你自己的回撥吧~
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式