我們先來看下維基百科的定義:
在計算機程序設計中,回調函數,或簡稱回調(call),是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計允許了底層代碼調用在高層定義的子程序。
這種標準的定義,大多數時候說的都比較抽象,下面我們以實際生活中的例子來講解到底什麼是回調函數。
回調函數的用途十分廣泛,在各種編程語言裏面都有體現,有點類似Spring裏面IOC(inversion of control=控制反轉)的概念,本身是一個非常簡單的概念,看下面的一個例子:
假設一個場景:
老師給學生布置了作業,學生收到作業後開始寫作業,寫完之後通知老師查看,老師查看之後就可以回家。
回調的概念,在這裏面就體現的淋漓盡致,在這裏面有兩個角色,一個是老師,一個是學生。老師有兩個動作,第一個是佈置作業,第二個是查看作業。而學生有一個動作是做作業, 那麼問題來了,老師並不知道學生何時才能做完作業,所以比較優雅的解決辦法是等學生的通知,也就是學生做完之後告訴老師就可以。這就是典型的回調理念。
那麼在編程中,該如何體現? 從上面的分析中,可以得出來回調模式是雙方互通的,老師給學生布置作業,學生做完通知老師查看作業。 關於回調,這裏面還分同步回調和異步回調兩種模式:
同步模式:
如果老師在放學後,給學生布置作業,然後一直等待學生完成後,才能回家,那麼這種方法就是同步模式。
異步模式:
如果老師在放學後,給學生布置作業,這個時候老師並不想等待學生完成,而是直接就回家了,但告訴學生,如果完成之後發短信通知自己查看。這種方式就是異步的回調模式。
回調模式爲了不影響主任務執行,一般會設計成異步任務。下面我們看下在Java中,模擬上面舉的例子實現一個簡單的回調,包括同步和異步兩種模式:
首先,回調的方法我們最好定義成一個接口,這樣便於擴展:
/*** *通過接口定義回調函數 */ public interface CallBack { //檢查作業屬於老師的功能,但由學生觸發,故稱回調 public void checkWork(); }
然後,我們定義老師的角色:
package design_pattern.callback.demo2; public class Teacher implements CallBack { private Student student; public Teacher(Student student) { this.student = student; } /*** * 給學生分配作業 * @param isSync true=同步回調 false=異步回調 * @throws InterruptedException */ public void assignWork(boolean isSync) throws InterruptedException { System.out.println("老師分配作業完成...."); if(isSync){ student.doWrok(this);//同步通知做作業 }else{ student.asynDoWrok(this);//異步通知做作業 } System.out.println("老師回家了...."); } @Override public void checkWork() { System.out.println("老師收到通知並查看了學生的作業!"); } }
上面定義的是老師角色,有兩個行爲,一個是佈置作業,一個是檢查作業,佈置作業裏面,在佈置作業裏面,老師可以選擇同步回調還是異步回調。
接着我們看下學生角色如何定義:
public class Student { public void doWrok(CallBack callBack) throws InterruptedException { System.out.println("學生開始做作業....."); TimeUnit.SECONDS.sleep(3); System.out.println("學生完成作業了,通知老師查看"); callBack.checkWork(); //通知老師查看作業 } public void asynDoWrok(CallBack callBack) throws InterruptedException { //通過一個線程來異步的執行任務 Runnable runnable= new Runnable(){ @Override public void run() { System.out.println("學生開始做作業....."); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("學生完成作業了,通知老師查看"); callBack.checkWork(); } }; Thread thread=new Thread(runnable); thread.start(); } }
學生角色裏面只有一個行爲做作業,但這裏我提供了兩種模式,一個是同步,一個是異步。
最後,我們來測試下:
public class AppMain { public static void main(String[] args) throws InterruptedException { Student student=new Student();//學生角色 System.out.println("\n===============同步模式================"); Teacher teacher=new Teacher(student);//老師角色 //同步回調模式,老師給學生布置作業,老師等學生完成之後才能回家 teacher.assignWork(true); System.out.println("\n===============異步模式================"); //異步回調模式,老師給學生布置作業,佈置完成之後就可以回家,學生完成之後會通知老師查看。 teacher.assignWork(false); } }
執行結果如下:
===============同步模式================ 老師分配作業完成.... 學生開始做作業..... 學生完成作業了,通知老師查看 老師收到通知並查看了學生的作業! 老師回家了.... ===============異步模式================ 老師分配作業完成.... 老師回家了.... 學生開始做作業..... 學生完成作業了,通知老師查看 老師收到通知並查看了學生的作業!
對於同步和異步兩種模式的結果,在上面的輸出內容裏面可以非常清晰的看出來區別,也體現回調的雙通模式。老師角色持有了學生對象的引用,並告訴學生做作業,而同時學生角色,也持有老師角色的引用,可以在自己完成作業後,告訴老師查看作業。
總結:
回調模式,在生活中的例子非常常見,在編程中最常見的就是各種GUI編程裏面的按鈕點擊什麼的,通過回調可以將控制權轉移,配合上異步模式,可以讓系統設計的更加優雅。