回调函数
先从生活中的例子来理解这种过程:
我点了一份外卖,外卖到了外卖小哥会自动拨打我的电话通知我去拿外卖。
这个过程就是回调。
OK,这是一个simple的过程,那么用代码来实现如何实现。
实现一个简单的回调函数模型
- UserOrder
package com.xjm.design.eventlistener.callback;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author jaymin
* 客户,负责点餐和留下联系方式
* 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());
}
/**
* 联系方式,配送员送到之后通过这个方法通知客户
*/
public void callback(){
System.out.println("食物已到达,请下楼取餐");
}
}
- DeliveryPerson
package com.xjm.design.eventlistener.callback;
/**
* @author jaymin
* 配送员,负责按照客户的要求进行配送,配送完后通知客户进行用餐。
* 2021/1/10 21:17
*/
public class DeliveryPerson {
public void supply(UserOrder userOrder){
System.out.println("当前用户下单的食品清单:"+userOrder.getFoodName());
System.out.println("到达商家拿到食物");
System.out.println("抵达客户留下的地址,通知客户进行取餐");
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
这样有什么坏处?硬编码了,不利于扩展.下面我们通过接口来实现多态.
重构回调函数
- 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
* 配送员,负责按照客户的要求进行配送,配送完后通知客户进行用餐。
* 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("当前用户下单的食品清单:" + foodName);
System.out.println("到达商家拿到食物");
System.out.println("抵达客户留下的地址,通知客户进行取餐");
callback.callback();
}
}
- CallbackDemo
这里使用lambda来代替内部类,写法上更加简洁.
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("食物已到达,请下楼取餐"),"冰红茶").execute();
}
}
调用过程
JDK中的回调函数-Runnable接口
Thread类中内置了一个
private Runnable target;
,在start的时候会回调Runnable接口的run方法.
new Thread(()-> System.out.println("callback")).start();
扩展阅读
java 回调函数解读
事件监听器模式
由一组监听器订阅特定事件的发布,一旦该事件进行了发布,所有的监听器都会做出响应,其中的响应则是上文所述的回调函数.
事件监听器模式组成成员
- 事件源: Event Source.被监听的对象,一旦事件源发生某个动作时,则调用其内置的事件监听器的方法,将事件对象进行广播.
- 事件监听器: Event Listener. 监听事件源,可以对事件进行判断,进而响应.
- 事件对象: Event Object.事件,通常为事件源广播的内容。
代码示例
- Event
定义事件发布的内容
package com.tea.design.eventlistener.pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author jaymin
* 事件对象.
* 2021/1/11 22:10
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Event {
private String message;
}
- EventListener
定义事件监听器接口,声明处理事件的方法
package com.tea.design.eventlistener.pattern;
/**
* @author jaymin
* 事件监听器
* 2021/1/11 22:18
*/
public interface EventListener {
/**
* 处理事件
* @param event 事件
*/
void processEvent(Event event);
}
- OfflineNewsEventListener
监听事件进行日志打印
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("日报头条:今天大事件:{}",event.getMessage());
}
}
- OnlineNewsEventListener
监听事件进行日志打印
package com.tea.design.eventlistener.pattern;
import lombok.extern.slf4j.Slf4j;
/**
* @author jaymin
* 如果发生大新闻,网络传媒要处理报道.
* 2021/1/11 22:27
*/
@Slf4j
public class OnlineNewsEventListener implements EventListener {
@Override
public void processEvent(Event event) {
log.info("微博爆料:今天的爆炸新闻:{}", event.getMessage());
}
}
- EventSource
事件源,提供注册监听器方法与发布事件方法
package com.tea.design.eventlistener.pattern;
import java.util.ArrayList;
import java.util.List;
/**
* @author jaymin
* 2021/1/11 22:30
*/
public class EventSource {
/**
* 将所有的监听者进行存储
*/
private List listenerList = new ArrayList<>();
/**
* 注册监听者
* @param eventListener
*/
public void addListener(EventListener eventListener){
listenerList.add(eventListener);
}
/**
* 发布事件
* @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
总结
事件监听器模式与观察者模式大同小异,实现的思想是让被监听者/被观察者持有所有的监听器类,当需要是事件发布的时候,对这些监听器进行消息广播。
监听器实现至统一的接口,事件源会将事件对象作为参数进行传输,然后每个监听器处理自己对应的业务.
这体现了面向接口编程的设计原则,让代码耦合度更加松散。
缺点:
- 多个监听器操作同一事件对象,监听器与监听器无法感知对方对该对象进行了什么操作。
- 同步调用,如果部分监听器执行时间长,会增加耗时。可以采用异步处理的方式规避.
扩展阅读
- JDK中的观察者模式
- 消息队列的broker消费模式
- Spring容器的事件监听器应用