(轉!)Java回調機制解讀

大多數人25歲就已經死了,只是到75歲才埋。

 

轉自:https://www.cnblogs.com/xrq730/p/6424471.html

 

模塊間調用

在一個應用系統中,無論使用何種語言開發,必然存在模塊之間的調用,調用的方式分爲幾種:

(1)同步調用

同步調用是最基本並且最簡單的一種調用方式,類A的方法a()調用類B的方法b(),一直等待b()方法執行完畢,a()方法繼續往下走。這種調用方式適用於方法b()執行時間不長的情況,因爲b()方法執行時間一長或者直接阻塞的話,a()方法的餘下代碼是無法執行下去的,這樣會造成整個流程的阻塞。

(2)異步調用

異步調用是爲了解決同步調用可能出現阻塞,導致整個流程卡住而產生的一種調用方式。類A的方法方法a()通過新起線程的方式調用類B的方法b(),代碼接着直接往下執行,這樣無論方法b()執行時間多久,都不會阻塞住方法a()的執行。但是這種方式,由於方法a()不等待方法b()的執行完成,在方法a()需要方法b()執行結果的情況下(視具體業務而定,有些業務比如啓異步線程發個微信通知、刷新一個緩存這種就沒必要),必須通過一定的方式對方法b()的執行結果進行監聽。在Java中,可以使用Future+Callable的方式做到這一點,具體做法可以參見我的這篇文章Java多線程21:多線程下其他組件之CyclicBarrier、Callable、Future和FutureTask

(3)回調

 

最後是回調,回調的思想是:

  • 類A的a()方法調用類B的b()方法
  • 類B的b()方法執行完畢主動調用類A的callback()方法

這樣一種調用方式組成了上圖,也就是一種雙向的調用方式。

 

代碼示例

接下來看一下回調的代碼示例,代碼模擬的是這樣一種場景:老師問學生問題,學生思考完畢回答老師。

首先定義一個回調接口,只有一個方法tellAnswer(int answer),即學生思考完畢告訴老師答案:

複製代碼
1 /**
2  * 回調接口,原文出處http://www.cnblogs.com/xrq730/p/6424471.html
3  */
4 public interface Callback {
5 
6     public void tellAnswer(int answer);
7     
8 }
複製代碼

定義一個老師對象,實現Callback接口:

複製代碼
 1 /**
 2  * 老師對象,原文出處http://www.cnblogs.com/xrq730/p/6424471.html
 3  */
 4 public class Teacher implements Callback {
 5 
 6     private Student student;
 7     
 8     public Teacher(Student student) {
 9         this.student = student;
10     }
11     
12     public void askQuestion() {
13         student.resolveQuestion(this);
14     }
15     
16     @Override
17     public void tellAnswer(int answer) {
18         System.out.println("知道了,你的答案是" + answer);
19     }
20     
21 }
複製代碼

老師對象有兩個public方法:

(1)回調接口tellAnswer(int answer),即學生回答完畢問題之後,老師要做的事情

(2)問問題方法askQuestion(),即向學生問問題

接着定義一個學生接口,學生當然是解決問題,但是接收一個Callback參數,這樣學生就知道解決完畢問題向誰報告:

複製代碼
1 /**
2  * 學生接口,原文出處http://www.cnblogs.com/xrq730/p/6424471.html
3  */
4 public interface Student {
5     
6     public void resolveQuestion(Callback callback);
7     
8 }
複製代碼

最後定義一個具體的學生叫Ricky:

複製代碼
 1 /**
 2  * 一個名叫Ricky的同學解決老師提出的問題,原文出處http://www.cnblogs.com/xrq730/p/6424471.html
 3  */
 4 public class Ricky implements Student {
 5 
 6     @Override
 7     public void resolveQuestion(Callback callback) {
 8         // 模擬解決問題
 9         try {
10             Thread.sleep(3000);
11         } catch (InterruptedException e) {
12             
13         }
14         
15         // 回調,告訴老師作業寫了多久
16         callback.tellAnswer(3);
17     }
18 
19 }
複製代碼

在解決完畢問題之後,第16行向老師報告答案。

寫一個測試類,比較簡單:

複製代碼
 1 /**
 2  * 回調測試,原文出處http://www.cnblogs.com/xrq730/p/6424471.html
 3  */
 4 public class CallbackTest {
 5 
 6     @Test
 7     public void testCallback() {
 8         Student student = new Ricky();
 9         Teacher teacher = new Teacher(student);
10         
11         teacher.askQuestion();
12         
13     }
14     
15 }
複製代碼

代碼運行結果就一行:

知道了,你的答案是3

簡單總結、分析一下這個例子就是:

(1)老師調用學生接口的方法resolveQuestion,向學生提問

(2)學生解決完畢問題之後調用老師的回調方法tellAnswer

這樣一套流程,構成了一種雙向調用的關係。

 

代碼分析

分析一下上面的代碼,上面的代碼我這裏做了兩層的抽象:

(1)將老師進行抽象

  • 將老師進行抽象之後,對於學生來說,就不需要關心到底是哪位老師詢問我問題,只要我根據詢問的問題,得出答案,然後告訴提問的老師就可以了,即使老師換了一茬又一茬,對我學生而言都是沒有任何影響的

(2)將學生進行抽象

  • 將學生進行抽象之後,對於老師這邊來說就非常靈活,因爲老師未必對一個學生進行提問,可能同時對Ricky、Jack、Lucy三個學生進行提問,這樣就可以將成員變量Student改爲List<Student>,這樣在提問的時候遍歷Student列表進行提問,然後得到每個學生的回答即可

這個例子是一個典型的體現接口作用的例子,之所以這麼說是因爲我想到有些朋友可能不太明白接口的好處,不太明白接口好處的朋友可以重點看一下這個例子,多多理解。

總結起來,回調的核心就是回調方將本身即this傳遞給調用方,這樣調用方就可以在調用完畢之後告訴回調方它想要知道的信息。回調是一種思想、是一種機制,至於具體如何實現,如何通過代碼將回調實現得優雅、實現得可擴展性比較高,一看開發者的個人水平,二看開發者對業務的理解程度。

 

同步回調與異步回調

上面的例子,可能有人會提出這樣的疑問:

這個例子需要用什麼回調啊,使用同步調用的方式,學生對象回答完畢問題之後直接把回答的答案返回給老師對象不就好了?

這個問題的提出沒有任何問題,可以從兩個角度去理解這個問題。

首先,老師不僅僅想要得到學生的答案怎麼辦?可能這個老師是個更喜歡聽學生解題思路的老師,在得到學生的答案之前,老師更想先知道學生姓名和學生的解題思路,當然有些人可以說,那我可以定義一個對象,裏面加上學生的姓名和解題思路不就好了。這個說法在我看來有兩個問題:

(1)如果老師想要的數據越來越多,那麼返回的對象得越來越大,而使用回調則可以進行數據分離,將一批數據放在回調方法中進行處理,至於哪些數據依具體業務而定,如果需要增加返回參數,直接在回調方法中增加即可

(2)無法解決老師希望得到學生姓名、學生解題思路先於學生回答的答案的問題

因此我認爲簡單的返回某個結果確實沒有必要使用回調而可以直接使用同步調用,但是如果有多種數據需要處理且數據有主次之分,使用回調會是一種更加合適的選擇,優先處理的數據放在回調方法中先處理掉。

另外一個理解的角度則更加重要,就是標題說的同步回調和異步回調了。例子是一個同步回調的例子,意思是老師向Ricky問問題,Ricky給出答案,老師問下一個同學,得到答案之後繼續問下一個同學,這是一種正常的場景,但是如果我把場景改一下:

老師並不想One-By-One這樣提問,而是同時向Ricky、Mike、Lucy、Bruce、Kate五位同學提問,讓同學們自己思考,哪位同學思考好了就直接告訴老師答案即可。

這種場景相當於是說,同學思考完畢完畢問題要有一個辦法告訴老師,有兩個解決方案:

(1)使用Future+Callable的方式,等待異步線程執行結果,這相當於就是同步調用的一種變種,因爲其本質還是方法返回一個結果,即學生的回答

(2)使用異步回調,同學回答完畢問題,調用回調接口方法告訴老師答案即可。由於老師對象被抽象成了Callback接口,因此這種做法的擴展性非常好,就像之前說的,即使老師換了換了一茬又一茬,對於同學來說,只關心的是調用Callback接口回傳必要的信息即可

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