其實對於回調機制,在實際使用中還是經常用到的。但好笑的是,一直沒能對所謂的回調的概念有一個很清晰的理解。
最近抽空看一些書的時候,老是時不時的提到回調的概念。那好吧,正好抽空來簡單總結總結,加深一下印象和理解~
網上的百科之類的資料中,看到的對於回調比較書面和規範的解釋是:
在計算機程序設計中,回調函數是指通過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計允許了底層代碼調用在高層定義的子程序。
不知道別人對於這樣比較“官方的”概念是一種怎麼樣的感受,反正我是“一頭包”,個人十分討厭這種書面解釋。
於是又在網上看了一些別人寫的關於回調的文章,發現很久以前就有別人作了一個很形象的比喻:
某天,我打電話向你請教問題,當然是個難題,^_^,你一時想不出解決方法,我又不能拿着電話在那裏傻等。
於是我們約定:等你想出辦法後打手機通知我,這樣,我就掛掉電話辦其它事情去了。
過了XX分鐘,我的手機響了,你興高采烈的說問題已經搞定,應該如此這般處理。故事到此結束。
這個例子說明了“異步+回調”的編程模式。其中,你後來打手機告訴我結果便是一個“回調”過程;
我的手機號碼必須在之前就告訴你,這便是註冊回調函數;我的手機號碼應該有效並且手機能夠接收到你的呼叫,這是回調函數必須符合接口規範。
而該作者針對這一情況給出的例子如下,假設我是程序員A,以下是我的程序a:
public class Caller
{
public MyCallInterface mc;
public void setCallfuc(MyCallInterface mc)
{
this.mc= mc;
}
public void call(){
this.mc.method();
}
}
我還需要定義一個接口,以便程序員B根據我的定義編寫程序實現接口。
public interface MyCallInterface
{
public void method();
}
於是,程序員B只需要實現這個接口就能達到回調的目的了:
public class B implements MyCallInterface
{
public void method()
{
System.out.println("回調");
}
public static void main(String args[])
{
Caller call = new Caller();
call.setCallfuc(new B());
call.call();
}
}
看完這個例子過後,對於回調似乎有一定的理解了,但似乎卻還是處於一種似懂非懂的狀態,蛋疼中...
於是又繼續查找了一些資料,然後發現,上面的例子對於“回調”思想體現的重點大致在於:
在Java當中,一般來說類的成員變量一般都是數據對象,主要是用來傳遞數據用的。
而回調的意思是:把一段程序作爲成員變量,在特定的場合使用該段程序。這就是回調的核心。
這就剛好對應了上面講到的,網上的百科資料中對於回調比較“書面”和“官方”的解釋了。
所以,大致來講對於上面的例子中:
程序員A就是想要把一段程序,也就是函數“method”作爲成員變量來使用;但是呢,Java的語言特性導致了一段程序是不能單獨作爲變量和參數來使用的。
那麼,基於面向對象的特性,就只能將其封裝成類也就是對象來使用。但是這段代碼在程序員A進行編程的時候又是無法確定的,它只能提供一個入口(即聲明),讓B去實現。
這種特點恰恰適合於Java當中Interface的特點。於是,函數“method”被封裝在了接口“MyCallInterface”當中。
到了現在,可算明白了,“method”方法就是這裏的所謂的“回調函數”。
而這個例子中,整個的回調過程,就可以簡單的分解爲:
程序員A遇到難題,無法解決,於是請教程序員B = ProgrammerA call toProgrammerB
程序員B收到問題,進行思考,最終給出解決方案 = ProgrammerB call toProgrammerA
當程序員給出解決方案,通知給程序員A時,這個過程就是所謂的回調了。
當我對這個例子進行了一番琢磨後,感覺就是:
對於打電話請教問題的這個經典用例自身,是很能夠描述回調的思想的。但是,對於這個例子來說,
對於回調的說明似乎並不能得到給人一種醍醐灌頂的體驗。
至少我是這樣覺得的,所謂回調回調的,通常第一感覺就是:
既然要回,那麼就如同打電話請教問題的例子一樣,
我打電話給你了,你得回電給我,這纔算是完成了一個回調嘛。
但在上面他的例子中,這個“回電”的過程沒有一個很明顯的體現。
另一種場景似乎更能更加明顯直接的對於回調進行說明。
在其它的博客裏看了對於回調的經典應用場景有這樣一種說法:
- Class A實現接口CallBack callback——背景1
- class A中包含一個class B的引用b ——背景2
- class B有一個參數爲callback的方法f(CallBack callback) ——背景3
- A的對象a調用B的方法 f(CallBack callback) ——A類調用B類的某個方法 C
- 然後b就可以在f(CallBack callback)方法中調用A的方法 ——B類調用A類的某個方法D
對於這種應用場景,我們來結合一些“身邊的實際現象”,就能很好的幫助自身進行理解。
最近恰逢年初,又是一年跳槽的高峯期了。
所以對於員工離職與跳槽的現象,其實也可以幫助我們對“回調”進行理解。
跳槽的很常見的原因自然就是薪資問題,那麼:
假設我們作爲一個員工,又是新的一年了,工資依舊可憐巴巴,心裏捉急啊。
這時怎麼辦呢?跳吧?那萬一剛跳,薪資又要調整了呢?
於是,決定了,先和BOSS“談判”,根據結果再做最後的決定。
這個時候,實際上也就是所謂的一種回調。
我們以一個程序猿的角度來分解這個問題:
首先,員工想要從老闆處得知關於薪資調整的結果,那麼就需要一個“途徑”,於是出現了一個回調接口:
/**
* 回調接口
* @author hql
* 2015/03/03
*/
public interface Negotiation {
/*
* 實際這就是你向BOSS提供的回覆你調薪詢問的一種途徑
* BOSS根據該途徑給你一個結果,而你就可以通過該結果來做出決定
*/
public void setNewSalary(int salary);
}
接下來,就是員工類的定義:
/**
* 員工類
* @author hql
* 2015/03/03
*/
public class Employee implements Negotiation {//背景1
private Boss boss = new Boss(); //背景2
private int newSalary;
/*
* 開啓談判
*/
void startNegotiation() {
new Thread(new Runnable() {
@Override
public void run() {
//背景:Class A調用Class B中的某個方法C
boss.doNegotiation(Employee.this);
}
}).start();
}
/*
* 回調函數,從BOSS處獲取薪資調整結果
*/
@Override
public void setNewSalary(int salary) {
this.newSalary = salary;
makeDecision();
}
/*
* 根據薪資調整結果做出最終決定
*/
void makeDecision() {
if (newSalary >= 10000) {
System.out.println("調薪滿意,繼續奮鬥!");
} else {
System.out.println("呵呵,拜拜了您!");
}
}
}
緊接着,是老闆類:
/**
* 老闆類
* @author hql
* 2015/03/03
*/
public class Boss {
public void doNegotiation(Negotiation negotiation) {//背景3
System.out.println("小子去年乾的不錯,漲!");
// Boss call back to Employee - 回調
negotiation.setNewSalary(10000);
}
}
一切準備工作就緒,接下來就是真正的“談判了”:
/**
* 回調函數測試類
* @author hql
* 2015/03/03
*/
public class TestCallBackFunc {
public static void main(String[] args) {
Employee xiaoli = new Employee();
xiaoli.startNegotiation();
}
}
到了這裏,終於對所謂的“回調機制”有了一個較爲清晰的理解了。正如上面的例子當中,所謂回調的過程就是指:
首先,是員工類(Class A)當中調用了老闆類(Class B)的方法:“doNegotiation”,由此向老闆提出調薪的要求。(employee call to boss)
然後,老闆收到請求,做出決定。於是老闆類(Class B)回過頭又調用了員工類(Class A)當中的方法:“setNewSalary”。(boss call to employee)
所以,其實針對回調,如果我們作更通俗的理解,其實就是:
一個員工想要調薪,針對於調薪,就自然會有一個結果,但是這個結果步是員工自身能決定的,需要由BOSS來拍板。
那麼,自然的,針對於該調薪結果,就需要一個“介質”,也可以說是一個“渠道”來在員工與BOSS之間進行溝通。
而“回調函數”在整個回調過程中,起到的作用就是這個“介質”。
我們不妨把上面的例子當中的回調函數“setNewSalary”視作一張“員工薪資調整申請表”,那麼整個回調過程就變成了:
某員工想要申請薪資調整,於是找到老闆(調用BOSS類當中的方法),告訴BOSS,我想要調薪了。
老闆收到了請求,做出了決定。現在要將這個結果返回該員工,這個返回的過程就被叫做回調。
但這個返回結果的方式肯定應該符合一定的規範,因爲至少要讓該員工意識到,BOSS已經針對於你的要求給出結果了。
“員工薪資調整申請表”就是該公司針對於“調薪結果”的提出的規範,員工根據該申請表就能得知自己的調薪結果。
所以說,之所以我們在程序中定義回調接口,其實就是在聲明一種規範,確保程序的嚴謹性。
試想一下,該老闆給出結果過後,正好碰到某個保潔人員,於是對這個清潔人員,正好,你去告訴某某員工他的調薪結果是xxxxx,那合理嗎?