猫头鹰的深夜翻译:设计模式EventBus

前言

今天,咱们将介绍一个比较新的设计模式(也就是没有在GoF那本书中出现过的一种设计模式),这个设计模式就是Event Bus设计模式。java

起源

假设一个大型应用中,有大量的组件彼此间存在交互。而你但愿可以在组件通讯的同时可以知足低耦合和关注点分离原则。Event Bus设计模式是一个很好的解决方案。面试

Event Bus的概念和网络中的总线拓扑概念相似。即存在某种管道,而全部的电脑都链接在这条管道之上。其中的任何一台电脑发送的消息都将分发给总线上全部其它的主机。而后,每台主机决定是否接收仍是抛弃掉这条消息。设计模式

clipboard.png

在组件的层面上也是相似的:主机对应着应用的组件,消息对应于事件(event)或者数据。而管道是Event Bus对象。微信

实现

并无一种绝对正确的实现EventBus的方式。在这里我会简单的介绍两种方法。网络

第一种方法

这是比较经典的一种方法,它主要取决于定义EventBus接口(从而强制实现一个特定的协议),按需对其实现,而后定义一个Subscriber(另外一份协议)来进行Event的处理。ide

/**
 * interface describing a generic event, and it's associated meta data, it's this what's going to
 * get sent in the bus to be dispatched to intrested Subscribers
 *
 * @author chermehdi
 */
public interface Event<T> {
  /**
   * @returns the stored data associated with the event
   */
  T getData();
}
import java.util.Set;
/**
 * Description of a generic subscriber
 *
 * @author chermehdi
 */
public interface Subscribable {
  /**
   * Consume the events dispatched by the bus, events passed as parameter are can only be of type
   * declared by the supports() Set
   */
  void handle(Event<?> event);
  /**
   * describes the set of classes the subscribable object intends to handle
   */
  
  Set<Class<?>> supports();
}
import java.util.List;
/**
 * Description of the contract of a generic EventBus implementation, the library contains two main
 * version, Sync and Async event bus implementations, if you want to provide your own implementation
 * and stay compliant with the components of the library just implement this contract
 *
 * @author chermehdi
 */
public interface EventBus {
  /**
   * registers a new subscribable to this EventBus instance
   */
  void register(Subscribable subscribable);
  /**
   * send the given event in this EventBus implementation to be consumed by interested subscribers
   */
  void dispatch(Event<?> event);
  /**
   * get the list of all the subscribers associated with this EventBus instance
   */
  List<Subscribable> getSubscribers();
}

Subscribable接口定义了一个方法来处理一个特定类型的消息,而且经过定义supports方法决定支持哪一种类型。post

EventBus的实现持有全部Subscribable对象,而且每当一个新事件触发dispatch方法时,通知全部的Subscribable对象。this

这种方案的有点事能够在编译时检查传递过来的Subscribable对象,并且更加符合面向对象的思想,由于无需使用反射。同时,能够看到,这种方案更容易实现。缺点是接口的强制性--你老是须要一个新的类来处理一种类型的Event,在项目初期这个问题可能不明显,可是随着项目发展,你会发现,新建一个类只是为了处理简单的逻辑好比日志或是数据分析会显得很冗余。spa

第二种方法

这个方法来源于Guava的实现。EventBus看上去更简单更好用,对于每一个时间的consumer, 你只须要经过对一个方法加上@Subscribe注解,而且在注解的参数中传入你但愿处理的对象类型(单个对象/参数)。而后你经过调用eventBus.register(objectContainingTheMethod)来注册事件的消费者。要产生一个新的时间,你只须要调用eventBus.post(someObject),而后全部相关的消费者都将会被通知。设计

若是对应一个特定的对象没有对应的消费者怎么办?在guava的实现中,它们被称为DeadEvents,在个人实现中,post调用会被忽略。

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
 * Simple implementation demonstrating how a guava EventBus works generally, without all the noise
 * of special cases handling, and special guava collections
 *
 * @author chermehdi
 */
public class EventBus {
  private Map<Class<?>, List<Invocation>> invocations;
  private String name;
  public EventBus(String name) {
    this.name = name;
    invocations = new ConcurrentHashMap<>();
  }
  public void post(Object object) {
    Class<?> clazz = object.getClass();
    if (invocations.containsKey(clazz)) {
      invocations.get(clazz).forEach(invocation -> invocation.invoke(object));
    }
  }
  public void register(Object object) {
    Class<?> currentClass = object.getClass();
    // we try to navigate the object tree back to object ot see if
    // there is any annotated @Subscribe classes
    while (currentClass != null) {
      List<Method> subscribeMethods = findSubscriptionMethods(currentClass);
      for (Method method : subscribeMethods) {
        // we know for sure that it has only one parameter
        Class<?> type = method.getParameterTypes()[0];
        if (invocations.containsKey(type)) {
          invocations.get(type).add(new Invocation(method, object));
        } else {
          List<Invocation> temp = new Vector<>();
          temp.add(new Invocation(method, object));
          invocations.put(type, temp);
        }
      }
      currentClass = currentClass.getSuperclass();
    }
  }
  private List<Method> findSubscriptionMethods(Class<?> type) {
    List<Method> subscribeMethods = Arrays.stream(type.getDeclaredMethods())
        .filter(method -> method.isAnnotationPresent(Subscribe.class))
        .collect(Collectors.toList());
    checkSubscriberMethods(subscribeMethods);
    return subscribeMethods;
  }
  private void checkSubscriberMethods(List<Method> subscribeMethods) {
    boolean hasMoreThanOneParameter = subscribeMethods.stream()
        .anyMatch(method -> method.getParameterCount() != 1);
    if (hasMoreThanOneParameter) {
      throw new IllegalArgumentException(
          "Method annotated with @Susbscribe has more than one parameter");
    }
  }
  public Map<Class<?>, List<Invocation>> getInvocations() {
    return invocations;
  }
  public String getName() {
    return name;
  }
}

能够看到这种方案所须要的额外工做比较少。你只须要定义方法的名称而不是为各个处理器命名。并且你能够将全部的消费者定义在一个类中。你只须要为每一个方法传递不一样的事件类型便可。

clipboard.png
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注个人微信公众号!将会不按期的发放福利哦~

相关文章
相关标签/搜索