回調(diào)函數(shù)
先從生活中的例子來理解這種過程:
我點(diǎn)了一份外賣,外賣到了外賣小哥會(huì)自動(dòng)撥打我的電話通知我去拿外賣。
這個(gè)過程就是回調(diào)。
OK,這是一個(gè)simple的過程,那么用代碼來實(shí)現(xiàn)如何實(shí)現(xiàn)。
實(shí)現(xiàn)一個(gè)簡單的回調(diào)函數(shù)模型
- UserOrder
package com.xjm.design.eventlistener.callback;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author jaymin<br>
* 客戶,負(fù)責(zé)點(diǎn)餐和留下聯(lián)系方式<br>
* 2021/1/10 21:16
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserOrder {
/**
* 食物名稱
*/
private String foodName;
public UserOrder(DeliveryPerson deliveryPerson){
deliveryPerson.supply(UserOrder.builder().foodName("冰紅茶").build());
}
/**
* 聯(lián)系方式,配送員送到之后通過這個(gè)方法通知客戶
*/
public void callback(){
System.out.println("食物已到達(dá),請(qǐng)下樓取餐");
}
}
- DeliveryPerson
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin<br>
* 配送員,負(fù)責(zé)按照客戶的要求進(jìn)行配送,配送完后通知客戶進(jìn)行用餐。<br>
* 2021/1/10 21:17
*/
public class DeliveryPerson {
public void supply(UserOrder userOrder){
System.out.println("當(dāng)前用戶下單的食品清單:"+userOrder.getFoodName());
System.out.println("到達(dá)商家拿到食物");
System.out.println("抵達(dá)客戶留下的地址,通知客戶進(jìn)行取餐");
userOrder.callback();
}
}
- CallbackDemo
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin
* 2021/1/10 21:34
*/
public class CallbackDemo {
public static void main(String[] args) {
UserOrder userOrder = new UserOrder(new DeliveryPerson());
}
}
- Result

這樣有什么壞處?硬編碼了,不利于擴(kuò)展.下面我們通過接口來實(shí)現(xiàn)多態(tài).
重構(gòu)回調(diào)函數(shù)
- Callback
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin
* 2021/1/10 21:41
*/
public interface Callback {
void callback();
}
- DeliveryPerson
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin<br>
* 配送員,負(fù)責(zé)按照客戶的要求進(jìn)行配送,配送完后通知客戶進(jìn)行用餐。<br>
* 2021/1/10 21:17
*/
public class DeliveryPerson {
private String foodName;
private Callback callback;
public DeliveryPerson(Callback callback, String foodName) {
this.callback = callback;
this.foodName = foodName;
}
public void execute() {
System.out.println("當(dāng)前用戶下單的食品清單:" + foodName);
System.out.println("到達(dá)商家拿到食物");
System.out.println("抵達(dá)客戶留下的地址,通知客戶進(jìn)行取餐");
callback.callback();
}
}
- CallbackDemo
這里使用lambda來代替內(nèi)部類,寫法上更加簡潔.
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin
* 2021/1/10 21:34
*/
public class CallbackDemo {
public static void main(String[] args) {
new DeliveryPerson(()-> System.out.println("食物已到達(dá),請(qǐng)下樓取餐"),"冰紅茶").execute();
}
}
調(diào)用過程

JDK中的回調(diào)函數(shù)-Runnable接口
Thread類中內(nèi)置了一個(gè)
private Runnable target;,在start的時(shí)候會(huì)回調(diào)Runnable接口的run方法.
new Thread(()-> System.out.println("callback")).start();
擴(kuò)展閱讀
事件監(jiān)聽器模式
由一組監(jiān)聽器訂閱特定事件的發(fā)布,一旦該事件進(jìn)行了發(fā)布,所有的監(jiān)聽器都會(huì)做出響應(yīng),其中的響應(yīng)則是上文所述的回調(diào)函數(shù).
事件監(jiān)聽器模式組成成員
- 事件源: Event Source.被監(jiān)聽的對(duì)象,一旦事件源發(fā)生某個(gè)動(dòng)作時(shí),則調(diào)用其內(nèi)置的事件監(jiān)聽器的方法,將事件對(duì)象進(jìn)行廣播.
- 事件監(jiān)聽器: Event Listener. 監(jiān)聽事件源,可以對(duì)事件進(jìn)行判斷,進(jìn)而響應(yīng).
- 事件對(duì)象: Event Object.事件,通常為事件源廣播的內(nèi)容。

代碼示例
- Event
定義事件發(fā)布的內(nèi)容
package com.tea.design.eventlistener.pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author jaymin<br>
* 事件對(duì)象.<br>
* 2021/1/11 22:10
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Event {
private String message;
}
- EventListener
定義事件監(jiān)聽器接口,聲明處理事件的方法
package com.tea.design.eventlistener.pattern;
/**
* @author jaymin<br>
* 事件監(jiān)聽器<br>
* 2021/1/11 22:18
*/
public interface EventListener {
/**
* 處理事件
* @param event 事件
*/
void processEvent(Event event);
}
- OfflineNewsEventListener
監(jiān)聽事件進(jìn)行日志打印
package com.tea.design.eventlistener.pattern;
import lombok.extern.slf4j.Slf4j;
/**
* @author jaymin
* 2021/1/11 22:28
*/
@Slf4j
public class OfflineNewsEventListener implements EventListener{
@Override
public void processEvent(Event event) {
log.info("日?qǐng)?bào)頭條:今天大事件:{}",event.getMessage());
}
}
- OnlineNewsEventListener
監(jiān)聽事件進(jìn)行日志打印
package com.tea.design.eventlistener.pattern;
import lombok.extern.slf4j.Slf4j;
/**
* @author jaymin<br>
* 如果發(fā)生大新聞,網(wǎng)絡(luò)傳媒要處理報(bào)道.<br>
* 2021/1/11 22:27
*/
@Slf4j
public class OnlineNewsEventListener implements EventListener {
@Override
public void processEvent(Event event) {
log.info("微博爆料:今天的爆炸新聞:{}", event.getMessage());
}
}
- EventSource
事件源,提供注冊(cè)監(jiān)聽器方法與發(fā)布事件方法
package com.tea.design.eventlistener.pattern;
import java.util.ArrayList;
import java.util.List;
/**
* @author jaymin
* 2021/1/11 22:30
*/
public class EventSource {
/**
* 將所有的監(jiān)聽者進(jìn)行存儲(chǔ)
*/
private List<EventListener> listenerList = new ArrayList<>();
/**
* 注冊(cè)監(jiān)聽者
* @param eventListener
*/
public void addListener(EventListener eventListener){
listenerList.add(eventListener);
}
/**
* 發(fā)布事件
* @param event
*/
public void publishEvent(Event event){
listenerList.forEach(eventListener -> eventListener.processEvent(event));
}
}
- EventListenerDemo
package com.tea.design.eventlistener.pattern;
/**
* @author jaymin
* 2021/1/11 22:33
*/
public class EventListenerDemo {
public static void main(String[] args) {
EventSource eventSource = new EventSource();
OnlineNewsEventListener onlineNewsEventListener = new OnlineNewsEventListener();
OfflineNewsEventListener offlineNewsEventListener = new OfflineNewsEventListener();
eventSource.addListener(onlineNewsEventListener);
eventSource.addListener(offlineNewsEventListener);
eventSource.publishEvent(Event.builder().message("特朗普被推特永久封禁!").build());
}
}
- Result

總結(jié)
事件監(jiān)聽器模式與觀察者模式大同小異,實(shí)現(xiàn)的思想是讓被監(jiān)聽者/被觀察者持有所有的監(jiān)聽器類,當(dāng)需要是事件發(fā)布的時(shí)候,對(duì)這些監(jiān)聽器進(jìn)行消息廣播。
監(jiān)聽器實(shí)現(xiàn)至統(tǒng)一的接口,事件源會(huì)將事件對(duì)象作為參數(shù)進(jìn)行傳輸,然后每個(gè)監(jiān)聽器處理自己對(duì)應(yīng)的業(yè)務(wù).
這體現(xiàn)了面向接口編程的設(shè)計(jì)原則,讓代碼耦合度更加松散。
缺點(diǎn):
- 多個(gè)監(jiān)聽器操作同一事件對(duì)象,監(jiān)聽器與監(jiān)聽器無法感知對(duì)方對(duì)該對(duì)象進(jìn)行了什么操作。
- 同步調(diào)用,如果部分監(jiān)聽器執(zhí)行時(shí)間長,會(huì)增加耗時(shí)??梢圆捎卯惒教幚淼姆绞揭?guī)避.
擴(kuò)展閱讀
- JDK中的觀察者模式
- 消息隊(duì)列的broker消費(fèi)模式
- Spring容器的事件監(jiān)聽器應(yīng)用