观察者模式

结构图

代码实现

public abstract class Subject {

    private List<Observer> observerList = new ArrayList<Observer>();

    /**
* 注册观察者
* @param observer
*/
public void register(Observer observer) {
observerList.add(observer);
} /**
* 注销观察者
*
* @param observer
*/
public void unregister(Observer observer) {
observerList.remove(observer);
} /**
* 通知观察者更新
*/
public void post() {
for (Observer observer : observerList) {
observer.update();
}
} /**
* 获取被通知事件
*
* @return
*/
public abstract Object getEvent();
} public class ConcreteSubject1 extends Subject { /** 个性化的定制内容 */
private String subjectState; public ConcreteSubject1(String subjectState) {
this.subjectState = subjectState;
} @Override
public Object getEvent() {
System.out.println("Custom ConcreteSubject1");
return subjectState;
}
} public class ConcreteSubject2 extends Subject { private int subjectState; public ConcreteSubject2(int subjectState) {
this.subjectState = subjectState;
} @Override
public Object getEvent() {
System.out.println("Custom ConcreteSubject2");
return subjectState;
}
} public abstract class Observer { /** 用于观察者获取被通知的事件 */
protected Subject subject; /**
* 用于给Subject通知时调用的更新方法
*/
public abstract void update();
} public class ConcreteObserver1 extends Observer { public ConcreteObserver1(Subject subject) {
this.subject = subject;
} @Override
public void update() {
System.out.println("Subject " + subject.getEvent() + " ConcreteObserver1");
}
} public class ConcreteObserver1 extends Observer { public ConcreteObserver1(Subject subject) {
this.subject = subject;
} @Override
public void update() {
System.out.println("Subject " + subject.getEvent() + " ConcreteObserver1");
}
} public class ConcreteObserver1 extends Observer { public ConcreteObserver1(Subject subject) {
this.subject = subject;
} @Override
public void update() {
System.out.println("Subject " + subject.getEvent() + " ConcreteObserver1");
}
}

输出:

Custom ConcreteSubject1
Subject Sub1 ConcreteObserver1
Custom ConcreteSubject1
Subject Sub1 ConcreteObserver2

EventBus简单示例

EventBus是Guava提供的消息发布-订阅类库,它的工作机制类似于观察者模式,通过通知者去注册观察者,最后由通知者想观察者发布消息,示例代码

public class MsgCenter {

    /** EventBus的定位跟接近于消息中心,而他的post()方法跟接近于一个自定义的Subject */
public static EventBus eventBus = new EventBus();
} public class Observer1 { /**
* 只有通过@Subscribe注解的方法才会被注册进EventBus
* 而且方法有且只能有1个参数
* @param msg
*/
@Subscribe
public void ob1Mthod1(String msg) {
System.out.println(msg + " test1!");
} @Subscribe
public void ob1Method2(String msg) {
System.out.println(msg + " test2!");
}
} public class Observer2 {
@Subscribe
public void ob2Method1(String msg) {
System.out.println(msg + " test3!");
} // 错误的基本型参数
// @Subscribe
// public void ob2Method2(int msg) {
// System.out.println(msg + " test4!");
// }
/**
* post() 不支持自动装箱功能,只能使用Integer,不能使用int,否则handlersByType的Class会是int而不是Intege
* 而传入的int msg参数在post(int msg)的时候会被包装成Integer,导致无法匹配到
*/
@Subscribe
public void ob2Method2(Integer msg) {
System.out.println(msg + " test4!");
}
} public class Test { public static void main(String[] args) throws InterruptedException {
EventBus eventBus = new EventBus();
Observer1 observer1 = new Observer1();
Observer2 observer2 = new Observer2(); eventBus.register(observer1);
eventBus.register(observer2); // 只有注册的参数类型为String的方法会被调用
eventBus.post("post string method"); // 注销observer2
eventBus.unregister(observer2);
eventBus.post("post string method after unregister");
}
}

输出

post string method test2!
post string method test1!
post string method test3!
post string method after unregister test2!
post string method after unregister test1!

实际上EventBus要表达的意图很简单,就是将post(Object arg)这里的arg当做参数传入到已注册的方法(被@Subscribe)的方法里,并调用该方法,所以当post(String)的时候,调用的参数类型为String的注册方法,当post(int)的时候,调用则是参数类型为Integer的注册方法

EventBus的实现方式

eventbus的实现方式实际上类似于上例写的简单的观察者模式,不同点在于它实现了泛化的注册方法以及泛化的方法调用,另外还考虑到了多线程的问题,对多线程使用时做了一些优化

register(Object listener)

register()方法注册一个任意类型的实例并将其使用了@Subscribe的方法注册到一个Map中,这个Map使用方法的参数类型Class为Key,值为一个Set,这个Set包含了所有参数类型为Key的EventHandler,Eventhandler是EventBus定义的一个数据结构,由listener(方法拥有者instance,例如上例中的observer1)和这个listener的@Subscribe方法构成

]这个Map的结构示意图如下

其实现代码如下

  /**
* Registers all handler methods on {@code object} to receive events.
* Handler methods are selected and classified using this EventBus's
* {@link HandlerFindingStrategy}; the default strategy is the
* {@link AnnotatedHandlerFinder}.
*
* @param object object whose handler methods should be registered.
*/
public void register(Object object) {
handlersByType.putAll(finder.findAllHandlers(object));
} /**
* {@inheritDoc}
*
* This implementation finds all methods marked with a {@link Subscribe} annotation.
*/
@Override
public Multimap<Class<?>, EventHandler> findAllHandlers(Object listener) {
Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes(); for (Method method : clazz.getMethods()) {
/*
* Iterate over each distinct method of {@code clazz}, checking if it is annotated with
* @Subscribe by any of the superclasses or superinterfaces that declare it.
*/
for (Class<?> c : supers) {
try {
Method m = c.getMethod(method.getName(), method.getParameterTypes());
if (m.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method " + method
+ " has @Subscribe annotation, but requires " + parameterTypes.length
+ " arguments. Event handler methods must require a single argument.");
}
Class<?> eventType = parameterTypes[0];
EventHandler handler = makeHandler(listener, method); methodsInListener.put(eventType, handler);
break;
}
} catch (NoSuchMethodException ignored) {
// Move on.
}
}
}
return methodsInListener;
}

post(Object event)

post(Object event)方法用于向已注册的方法传递一个参数,并以此调用参数类型为event.class的所有方法,调用的时候会使用一个ThreadLocal的Queue来进行任务分发,这样的结果就是在多线程情况下,线程间共享注册方法的Map(上面提到那个),当时在发送消息时线程会保有自己独立的一个Post任务的Queue,保证了线程执行post()方法时候的独立性而不会相互影响,下面是多线程执行post()时的示意图

真正在执行post(Object event)的时候,会将msg与所有event.class对应的set里的所有method组合成一个EventBus.EventWithHandler对象并加入到ThreadLocal的Queue中,最后再将Queue出队依次执行这些方法,最后清空ThreadLocal的Queue,EventBus的实现代码如下

    /**
* Posts an event to all registered handlers. This method will return
* successfully after the event has been posted to all handlers, and
* regardless of any exceptions thrown by handlers.
*
* <p>If no handlers have been subscribed for {@code event}'s class, and
* {@code event} is not already a {@link com.google.common.eventbus.DeadEvent}, it will be wrapped in a
* DeadEvent and reposted.
*
* @param event event to post.
*/
@SuppressWarnings("deprecation") // only deprecated for external subclasses
public void post(Object event) {
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass()); boolean dispatched = false;
// 将event和所有event.class对应的方法组合成EventWithHandler并入队
for (Class<?> eventType : dispatchTypes) {
Set<EventHandler> wrappers = getHandlersForEventType(eventType); if (wrappers != null && !wrappers.isEmpty()) {
dispatched = true;
for (EventHandler wrapper : wrappers) {
enqueueEvent(event, wrapper);
}
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
} dispatchQueuedEvents();
} /**
* Queue the {@code event} for dispatch during
* {@link #dispatchQueuedEvents()}. Events are queued in-order of occurrence
* so they can be dispatched in the same order.
*/
void enqueueEvent(Object event, EventHandler handler) {
eventsToDispatch.get().offer(new EventWithHandler(event, handler));
} /**
* Drain the queue of events to be dispatched. As the queue is being drained,
* new events may be posted to the end of the queue.
*
* @deprecated This method should not be overridden outside of the eventbus package. It is
* scheduled for removal in Guava 14.0.
*/
@Deprecated
protected void dispatchQueuedEvents() {
// don't dispatch if we're already dispatching, that would allow reentrancy
// and out-of-order events. Instead, leave the events to be dispatched
// after the in-progress dispatch is complete.
if (isDispatching.get()) {
return;
} isDispatching.set(true);
try {
while (true) {
EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
if (eventWithHandler == null) {
break;
} dispatch(eventWithHandler.event, eventWithHandler.handler);
}
} finally {
isDispatching.set(false);
}
}

斜体加粗部分即为关键部分

EventBus多线程使用示例

public class Test2 {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread() {

            @Override
public void run() {
System.out.println(Thread.currentThread());
System.out.println("start");
Observer1 observer1 = new Observer1();
MsgCenter.eventBus.register(observer1);
MsgCenter.eventBus.post("post string");
System.out.println("end");
System.out.println();
}
}; Thread t2 = new Thread() { @Override
public void run() {
System.out.println(Thread.currentThread());
System.out.println("start");
Observer2 observer2 = new Observer2();
MsgCenter.eventBus.register(observer2);
MsgCenter.eventBus.post("post string2");
System.out.println("end");
System.out.println();
}
}; ExecutorService exec = Executors.newFixedThreadPool(2); exec.execute(t1); // 为何忽略多线程是run()方法的线程安全问题,让两个任务分开执行
TimeUnit.MILLISECONDS.sleep(500); exec.execute(t2); exec.shutdown();
}
}

输出

Thread[pool-1-thread-1,5,main]
start
post string test1!
post string test2!
end

Thread[pool-1-thread-2,5,main]
start
post string2 test1!
post string2 test2!
post string2 test3!
end

这里为了让执行结果更清晰,并没有让两个线程并发执行,但可以清楚看到他们是共享同一个注册方法的Map的,而由于post()分发消息时间的Queue是ThreadLocal的,这个Queue由每个线程独有

对于这里使用ThreadLocal的Queue的个人理解就是假如不使用ThreadLocal,共享同一个队列,就有可能由Thread1入队的EventWithHandler会由Thread2来执行,而由Thread2入队的EventWithHandler又有可能会由Thread1来执行,而造成执行秩序混乱.

而使用ThreadLocal的Queue,则这些EventWithHandler示例是由哪个线程入队就由哪个线程执行,同时不需要去考虑共享队列的入队和出队时的线程安全问题,也可以提升效率

观察者模式与Guava EventBus的更多相关文章

  1. 设计模式:Observer(观察者)—— Guava EventBus

    本文分为三个部分:   Observer(观察者) Guava EventBus详解 Guava EventBus使用示例   1. Observer(观察者)   1.1 背景   我们设计系统时, ...

  2. Guava 12:Guava EventBus源码剖析

    一.架构速读 传统上,Java的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的.设计EventBus就是为了取代这种显示注册方式,使组件间有了更好的解耦.EventBus不是通用型的发布-订 ...

  3. Guava - EventBus(事件总线)

    Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计 ...

  4. [Guava] EventBus

    1.  发布-订阅模式 发布-订阅模式(publish-subscribe)是一种编程范式,发布方不发布消息给特定的接收方,而是由订阅方选择性接收.这使得发布方和订阅方相对独立,减少了耦合性. 在发布 ...

  5. 观察者模式与Google Guava EventBus实现

    概述 观察者模式又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种. 它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态变化时,会 ...

  6. 使用Guava EventBus构建publish/subscribe系统

    Google的Guava类库提供了EventBus,用于提供一套组件内publish/subscribe的解决方案.事件总线EventBus,用于管理事件的注册和分发.在系统中,Subscribers ...

  7. Guava ---- EventBus事件驱动模型

    在软件开发过程中, 难免有信息的共享或者对象间的协作. 怎样让对象间信息共享高效, 而且耦合性低. 这是一个难题. 而耦合性高将带来编码改动牵一发而动全身的连锁效应. Spring的风靡正是由于攻克了 ...

  8. guava eventbus 原理+源码分析

    前言: guava提供的eventbus可以很方便的处理一对多的事件问题, 最近正好使用到了,做个小结,使用的demo网上已经很多了,不再赘述,本文主要是源码分析+使用注意点+新老版本eventbus ...

  9. guava EventBus 消息总线的运用

    public class Test { public static void main(String[] args) { final EventBus eventBus = new EventBus( ...

随机推荐

  1. elastucasearch基础理论以及安装

    一.elasticasearch核心概念 Near Realtime(NRT 近实时) Elasticsearch 是一个近实时的搜索平台.您索引一个文档开始直到它被查询时会有轻微的延迟时间(通常为1 ...

  2. 最短路算法 -- SPFA模板

    一.算法步骤 建立一个队列,初始时队列里只有起始点,再建立一个数组记录起始点到所有点的最短路径(该数组的初始值要赋为极大值,该点到它本身的路径赋为0,下面的模板中该数组为dist[]).然后执行松弛操 ...

  3. SVM 的推导、特点、优缺点、多分类问题及应用

    SVM有如下主要几个特点: (1)  非线性映射是SVM方法的理论基础,SVM利用内积核函数代替向高维空间的非线性映射: (2)  对特征空间划分的最优超平面是SVM的目标,最大化分类边际的思想是SV ...

  4. 百道CTF刷题记录(一)

    简介 最近在刷CTF题,主攻Web,兼职Misc Shiyanbar 0x01 简单的登陆题 简单概括: 考点: %00截断正则 CBC字节翻转攻击 难度: 难 WP:https://blog.csd ...

  5. React Native网络编程之Fetch

    目录 1.前言 2.什么是Fetch 3.最简单的应用 4.支持的请求参数 - 4.1. 参数详讲 - 4.2. 示例 5.请求错误与异常处理   1. 前言   网络请求是开发APP中不可或缺的一部 ...

  6. [转载] 你所不知道的TIME_WAIT和CLOSE_WAIT

    前言 本文转载自 https://mp.weixin.qq.com/s/FrEfx_Yvv0fkLG97dMSTqw.很久前看到Vincent Bernat在博客中写了一遍关于TCP time-wai ...

  7. Eclipse导入SVN项目的三种方式

    Eclipse导入SVN项目的三种方式 一.直接Import导入: 1.点击 File --> Import,进入导入项目窗口 2.选择从SVN检出项目,点击Next 3.选择创建新的资源库位置 ...

  8. 解决在ubuntu环境下, sublime不能输入中文的问题

    sublime text很好用,但是ubuntu下不能输入中文,这是一个很大的问题.网上已经有很多方法,这里将我自己使用的方法记录总结一下 首先,将你的操作系统升级到最新版: sudo apt-get ...

  9. IIS Server Farms入门

    概念了解 IIS Server Farms,实际上应该叫“Microsoft Web Farm Framework (WFF)”,依赖于“Web Platform Installer”才能安装,依赖于 ...

  10. 牛可乐发红包脱单OI赛 C 小可爱表白

    打个暴力查一下OEIS,5min做完 出题人一开始把式子打错了,一开始的式子的结果为$n * (n + 3) * 2^{n - 3}$ 我们考虑化式子 首先考虑 $\sum\limits_{j = 1 ...