什麼是回調函數(CallBack)

我們先來看下維基百科的定義:

在計算機程序設計中,回調函數,或簡稱回調(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編程裏面的按鈕點擊什麼的,通過回調可以將控制權轉移,配合上異步模式,可以讓系統設計的更加優雅。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章