一看就懂的观察者模式,带你用观察者模式手写一个最简单的EventBus事件总线框架
文章目录
一、什么是观察者模式
定义:在GOF中观察者模式定义是这样的:在多个对象之间,定义一个一对多的依赖,当一个对象状态改变时,所有依赖这个对象的对象都会自动收到通知。
观察者模式也称为发布订阅模式(Publish-Subscribe Design Pattern),一般被依赖的对象称为被观察者,依赖的对象称为观察者,不过也有其他的叫法,例如Subject和Observer,Publisher和Subscriber,Producer和Consumer,EvenEemitter(事件发布器)和EventListene,还有Dispatcher和Listener。只要场景符合观察者模式的定义,都算观察者模式的应用。
二、UML类图
- 主题Subject: 主题中包含着所有调用registerObservers来进行注册的 Observer(观察者) 主题收到消息后,通过notifyObservers方法,告知所有观察者其状态的改变。
- 观察者Observer: 包含着收到消息的处理逻辑,处理逻辑存在于其update方法中
三、典型代码实现(同步阻塞式)
/**
* 主题接口
*
* @author liuyp
* @date 2022/11/28
*/
public interface Subject<T> {
void registerObserver(Observer<T> obverser);
void removeObserver(Observer<T> obverser);
void notifyObservers(T message);
}
/**
* 观察者接口
*
* @author liuyp
* @date 2022/11/28
*/
public interface Observer<T> {
void update(T message);
}
/**
* 主题的具体实现
*
* @author liuyp
* @date 2022/11/28
*/
public class ConcreteSubject<T> implements Subject<T> {
/**
* 线程安全的Set容器,保存obversers
*/
private Set<Observer<T>> obversers = new CopyOnWriteArraySet<>();
@Override
public void registerObserver(Observer<T> obverser) {
obversers.add(obverser);
}
@Override
public void removeObserver(Observer<T> obverser) {
System.out.println("Obversable@" + this.hashCode() + " 移除观察者:" + obverser.hashCode());
obversers.remove(obverser);
}
@Override
public void notifyObservers(T message) {
System.out.println("Obversable@" + this.hashCode() + " 发布了一条消息:" + message.toString());
obversers.forEach(obverser -> obverser.update(message));
}
}
/**
* 具体的观察者
*
* @author liuyp
* @date 2022/11/28
*/
public class ConcreteObverser<T> implements Observer<T> {
@Override
public void update(T message) {
System.out.println("Obverser@" + this.hashCode() + " 收到通知:" + message);
}
}
/**
* 测试类
*
* @author liuyp
* @date 2022/11/28
*/
public class TestMain {
public static void main(String[] args) {
//定义主题 也是被观察者observable
Subject<String> subject = new ConcreteSubject<>();
//定义观察者 observer
Observer<String> observer1 = new ConcreteObverser<>();
Observer<String> observer2 = new ConcreteObverser<>();
//订阅主题 subject
subject.registerObserver(observer1);
subject.registerObserver(observer2);
//发布通知
subject.notifyObservers("消息1:明天是2022年11月29日");
//移除观察者1
subject.removeObserver(observer1);
//重新发布通知
subject.notifyObservers("消息2:琪琪农历10月17生日");
}
}
Obversable@1802598046 发布了一条消息:消息1:明天是2022年11月29日
Obverser@240650537 收到通知:消息1:明天是2022年11月29日
Obverser@483422889 收到通知:消息1:明天是2022年11月29日
Obversable@1802598046 移除观察者:240650537
Obversable@1802598046 发布了一条消息:消息2:琪琪农历10月17生日
Obverser@483422889 收到通知:消息2:琪琪农历10月17生日
四、观察者模式的作用
- 观察者模式的作用,主要就是将被观察者的事件消息,和观察者的行为进行解耦。控制代码的复杂性,提升可拓展性。(EP:例如被观察者是一个注册事件,观察者分别是发送短信提醒,和邮件通知,如果不引入观察者模式,那么发送短信和邮件通知逻辑将和注册事件耦合,如果引入第三个逻辑 例如发放优惠券,需要修改源注册事件代码。引入观察者模式后,只需要新增一个观察者,注册到用户注册的主题即可。易于拓展)
- 在第三节中的示例代码,是一个同步阻塞式的观察者模式(依次同步执行每个update方法),主要实现了解耦合的功能。如果使用异步非阻塞观察者模式(线程池执行每个update方法,或者update方法中使用线程)则除了解耦合外还能提高代码执行效率,减少响应时间。
- 除了同步阻塞式的观察者模式、异步非阻塞观察者模式外,如果涉及跨进程跨服务等的观察者模式,则一半引入消息队列,消息发送者和消费者完全解耦合。
五、利用观察者模式,实现事件总线EventBus
5.1 什么是EventBus 事件总线
- 事件总线,提供了实现观察者模式的“骨干代码”
- 基于事件总线框架,可以轻松地在业务中使用观察者模式,不需要手动从零开发
- Google Guava EventBus是一个著名的EventBus框架,支持异步非阻塞的观察者模式,也支持同步阻塞观察者模式。开源地址:https://github.com/google/guava/tree/master/guava/src/com/google/common/eventbus
5.2 试用EventBus
//第一步:声明EventBus
//声明同步阻塞式EventBus
EventBus eventBus=new EventBus();
//声明异步非阻塞式EventBus
EventBus eventBus=new AsyncEvnetBus(Executors.new FixedThreadPool(10));
//第二步:注册观察者
eventBus.register(obverser);
//第三步:发送事件
eventBus.post(userId);
5.3尝试实现简单地EventBus
EventBus的核心原理主要在其register方法和post方法
- EventBus框架,通过解析@Subscribe注解,生成Obverser注册表,这个注册表记录了消息类型,和可接收消息的函数对应关系。
- 调用post方法,发送消息时通过Obverser表找到对应的函数,通过java反射动态的执行函数。对于阻塞同步式观察者EventBus框架在一个线程内依次执行相应函数,对于异步非阻塞式则通过线程池执行相应的函数。
5.3.1 定义注解@Subscribe
/**
* 用在方法上的注解,标明该方法需要被注册作为obverse
* @author liuyp
* @date 2022/11/28
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
}
5.3.2 定义ObserverAction类
/**
* 主要用来表示使用@Subscribe标注的方法
* 其中target表示观察者类,method表示方法。
*
* @author liuyp
* @date 2022/11/29
*/
public class ObserverAction {
//标识使用@Subscribe标注的方法所在的类
private Object target;
//标识使用@Subscribe标注的方法
private Method method;
public ObserverAction(Object target, Method method) {
this.target = target;
this.method = method;
}
/**
* 执行
* event是method的参数,也就是观察者监听的Message
*
* @param event 事件
*/
public void execute(Object event) {
try {
method.invoke(target, event);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
5.3.3 定义观察者注册表类ObserverRegistry
/**
* 观察者注册表
*
* @author liuyp
* @date 2022/11/29
*/
public class ObserverRegistry {
/**
* 最关键的Obverser注册表
* 使用线程安全的 ConcurrentHashMap,和CopyOnWriteArraySet
* 以消息类型为Key 保存了可接收该类型的方法函数(ObverserAction对象)
*/
private ConcurrentMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();
/**
* 注册观察者对象
* 将对象中所有Subscribe方法保存到Obverser注册表
*
* @param observer 观察者
*/
public void register(Object observer) {
Map<Class<?>, Collection<ObserverAction>> allObserverActions = findAllObserverActions(observer);
for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : allObserverActions.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> eventActions = entry.getValue();
CopyOnWriteArraySet<ObserverAction> observerActions = registry.get(eventType);
if (observerActions == null) {
registry.putIfAbsent(eventType, new CopyOnWriteArraySet<>());
observerActions = registry.get(eventType);
}
observerActions.addAll(eventActions);
}
}
/**
* 根据事件消息类型,获取监听其对应类型的观察者方法列表
*
* @param event 事件
* @return {@link List}<{@link ObserverAction}>
*/
public List<ObserverAction> getMatchedObserverActions(Object event) {
List<ObserverAction> matchObservers = new ArrayList<>();
Class<?> postEventType = event.getClass();
for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {
Class<?> eventType = entry.getKey();
CopyOnWriteArraySet<ObserverAction> eventActions = entry.getValue();
if (eventType.isAssignableFrom(postEventType)) {
//方法接受的参数,是事件参数的子类,则返回这些Subscribe方法
matchObservers.addAll(eventActions);
}
}
return matchObservers;
}
/**
* 工具方法
* 对所有Subscribe方法,按照其接收的参数分组
*
* @param observer 观察者
* @return {@link Map}<{@link Class}<{@link ?}>, {@link Collection}<{@link ObserverAction}>>
*/
private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActions = new HashMap<>();
Class<?> clazz = observer.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
if (!observerActions.containsKey(eventType)) {
//处理边界问题
observerActions.put(eventType, new ArrayList<>());
}
observerActions.get(eventType).add(new ObserverAction(observer, method));
}
return observerActions;
}
/**
* 工具方法
* 得到观察者所在的类的所有的包含Subscribe注解的方法
*
* @param clazz clazz
* @return {@link List}<{@link Method}>
*/
private List<Method> getAnnotatedMethods(Class<?> clazz) {
List<Method> annotatedMethods = new ArrayList<>();
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(Subscribe.class)) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("方法:" + method + " 包含@Subscribe注解,但是有" + parameterTypes.length + "个参数");
}
annotatedMethods.add(method);
}
return annotatedMethods;
}
}
CopyOnWriteArraySet是一个线程安全的Set容器,写入时会根据原对象clone一个新的set,数据写入完成后,再替换原有Set以此解决并发读写问题,同时通过加锁的方式,避免了并发写冲突。
5.3.4 定义事件总线
/**
* 事件总线
*
* @author liuyp
* @date 2022/11/29
*/
public class EventBus {
//执行线程池
private Executor executor;
//观察者注册表
private ObserverRegistry registry = new ObserverRegistry();
public EventBus() {
//假线程池,直接使用调用线程同步执行
this(MoreExecutors.directExecutor());
}
protected EventBus(Executor executor) {
this.executor = executor;
}
/**
* 注册观察者对象
*
* @param object 对象
*/
public void register(Object object) {
registry.register(object);
}
/**
* 发送事件通知
*
* @param event 事件
*/
public void post(Object event) {
List<ObserverAction> matchedObserverActions = registry.getMatchedObserverActions(event);
for (ObserverAction matchedObserverAction : matchedObserverActions) {
executor.execute(() -> matchedObserverAction.execute(event));
}
}
}
5.3.4 定义异步事件总线
/**
* 异步事件总线
*
* @author liuyp
* @date 2022/11/29
*/
public class AsyncEventBus extends EventBus {
public AsyncEventBus(Executor executor) {
super(executor);
}
}
5.4 测试事件总线
/**
* 观察者类,使用Subscribe注解,将方法注册为观察者
* 类似Spring的EventListener
*
* @author liuyp
* @date 2022/11/29
*/
public class TestObserver {
@Subscribe
public void SubscribeStringEvent(String StringEvent) {
System.out.println("[线程 " + Thread.currentThread().getName() + "]我是@" + this.hashCode() + "监听到了String消息事件:" + StringEvent);
}
@Subscribe
public void SubscribeIntegerEvent(Integer integerEvent) {
System.out.println("[线程 " + Thread.currentThread().getName() + "]我是@" + this.hashCode() + "监听到了integer消息事件:" + integerEvent);
}
}
public class TestEventBus {
public static void main(String[] args) {
//声明同步事件总线
EventBus eventBus = new EventBus();
//声明异步事件总线
AsyncEventBus asyncEventBus = new AsyncEventBus(Executors.newFixedThreadPool(3));
//声明观察者对象
TestObserver testObserver = new TestObserver();
//注册观察者
eventBus.register(testObserver);
asyncEventBus.register(testObserver);
//向同步事件总线发布事件
eventBus.post("我是同步事件总线,我发了一条String消息");
eventBus.post("我是同步事件总线,我又发了一条String消息");
eventBus.post(1);
//向异步事件总线发布事件
asyncEventBus.post("我是异步事件总线,我发了一条String消息");
asyncEventBus.post("我是异步事件总线,我又发了一条String消息");
asyncEventBus.post(1);
}
}
[线程 main]我是@592179046监听到了String消息事件:我是同步事件总线,我发了一条String消息
[线程 main]我是@592179046监听到了String消息事件:我是同步事件总线,我又发了一条String消息
[线程 main]我是@592179046监听到了integer消息事件:1
[线程 pool-1-thread-1]我是@592179046监听到了String消息事件:我是异步事件总线,我发了一条String消息
[线程 pool-1-thread-2]我是@592179046监听到了String消息事件:我是异步事件总线,我又发了一条String消息
[线程 pool-1-thread-3]我是@592179046监听到了integer消息事件:1
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?