一看就懂的观察者模式,带你用观察者模式手写一个最简单的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生日

四、观察者模式的作用

  1. 观察者模式的作用,主要就是将被观察者的事件消息,和观察者的行为进行解耦。控制代码的复杂性,提升可拓展性。(EP:例如被观察者是一个注册事件,观察者分别是发送短信提醒,和邮件通知,如果不引入观察者模式,那么发送短信和邮件通知逻辑将和注册事件耦合,如果引入第三个逻辑 例如发放优惠券,需要修改源注册事件代码。引入观察者模式后,只需要新增一个观察者,注册到用户注册的主题即可。易于拓展)
  2. 在第三节中的示例代码,是一个同步阻塞式的观察者模式(依次同步执行每个update方法),主要实现了解耦合的功能。如果使用异步非阻塞观察者模式(线程池执行每个update方法,或者update方法中使用线程)则除了解耦合外还能提高代码执行效率,减少响应时间。
  3. 除了同步阻塞式的观察者模式、异步非阻塞观察者模式外,如果涉及跨进程跨服务等的观察者模式,则一半引入消息队列,消息发送者和消费者完全解耦合。



五、利用观察者模式,实现事件总线EventBus

5.1 什么是EventBus 事件总线

  1. 事件总线,提供了实现观察者模式的“骨干代码”
  2. 基于事件总线框架,可以轻松地在业务中使用观察者模式,不需要手动从零开发
  3. 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方法

  1. EventBus框架,通过解析@Subscribe注解,生成Obverser注册表,这个注册表记录了消息类型,和可接收消息的函数对应关系。
  2. 调用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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值