貓頭鷹的深夜翻譯:設計模式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
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~

相關文章
相關標籤/搜索