在平時寫代碼的過程中,我們需要實現這樣一種功能:當執行某個邏輯時,希望能夠進行其他邏輯的處理。最粗暴的方法是直接依賴其他模塊,調用該模塊的相應函數或者方法。但是,這樣做帶來一些問題。
- 模塊間相互依賴,耦合度高。以下訂單為例,訂單提交后需要進行支付以及進行一些其他處理,如發郵件等操作。相關的代碼可能是這樣。可以看到:訂單模塊依賴了支付服務以及用戶服務。
- 維護困難。由於模塊間相互依賴,當需要修改訂單邏輯時則需要修改submitOrder方法的源代碼,而某些時候可能無法修改。再者,如果有多個這種邏輯,修改時可能涉及到多處操作。
public class OrderPage {
private PaymentService paymentService;
private UserService userService;
public void submitOrder() {
Integer userId = 1;
BigDecimal amount = BigDecimal.TEN;
paymentService.doPayment(userId, amount);
userService.registerPayment(userId, amount);
}
}
public class PaymentService {
private MailService mailService;
public void doPayment(Integer userId, BigDecimal amount) {
//Do payment...
mailService.sendPaymentEmail(userId, amount);
}
}
public class UserService {
public String getEmailAddress(Integer userId) {
return "foo@bar.com";
}
public void registerPayment(Integer userId, BigDecimal amount) {
//Register payment in database...
}
}
public class MailService {
private UserService userService;
public void sendPaymentEmail(Integer userId, BigDecimal amount) {
String emailAddress = userService.getEmailAddress(userId);
//Send email...
}
}
一、觀察者模式
有時被稱作發布/訂閱模式,觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
通過觀察者模式來進行解耦,當對象發生變化時,通知其觀察者,由觀察者進行相應的處理。體現在訂單邏輯中時即為,定義多個觀察者觀察下訂單這個主題,當下訂單的動作發生時,通知其所有觀察者。再由每個觀察者進行處理。依據觀察者模式的實現,以上邏輯可改為如下代碼:
interface OrderListener {
public void onSubmitOrder(Integer userId, BigDecimal amount);
}
public class OrderPage {
private List<OrderListener> orderListeners=new ArrayList<OrderListener>();
public void submitOrder() {
Integer userId = 1;
BigDecimal amount = BigDecimal.TEN;
for (OrderListener orderListener : orderListeners) {
orderListener.onSubmitOrder(userId, amount);
}
}
public void addOrderListener(OrderListener orderListener){
this.orderListeners.add(orderListener);
}
}
class PaymentService implements OrderListener {
private MailService mailService;
public void doPayment(Integer userId, BigDecimal amount) {
// Do payment...
mailService.sendPaymentEmail(userId, amount);
}
@Override
public void onSubmitOrder(Integer userId, BigDecimal amount) {
doPayment(userId, amount);
}
}
class UserService implements OrderListener {
public String getEmailAddress(Integer userId) {
return "foo@bar.com";
}
public void registerPayment(Integer userId, BigDecimal amount) {
// Register payment in database...
}
@Override
public void onSubmitOrder(Integer userId, BigDecimal amount) {
registerPayment(userId, amount);
}
}
class MailService {
private UserService userService;
public void sendPaymentEmail(Integer userId, BigDecimal amount) {
String emailAddress = userService.getEmailAddress(userId);
// Send email...
}
}
可以看到,首先定義了OrderListener接口,接口中有一個onSubmitOrder方法。原始的實現中的PayService和UserService實現了該接口。OrderPage中維護了一個OrderListener列表,當提交訂單時調用所有監聽者的onSubmitOrder方法。可以看到此實現的訂單邏輯沒有直接依賴付款模塊和用戶模塊。 主程序通過添加監聽器來使其得到通知
public static void main(String[] args) {
PaymentService paymentService=new PaymentService();
UserService userService=new UserService();
OrderPage orderPage=new OrderPage();
orderPage.addOrderListener(paymentService);
orderPage.addOrderListener(userService);
}
二、Guava EventBus(監聽者模式的優雅實現)
雖然監聽者模式對源代碼進行了解耦,但是還是有一些不足。
- 相關模塊需要實現相應接口;
- 需要主動調用相關的addListener方法設置監聽器。
- 一個監聽器智能監聽一種操作.
EventBus是Guava對於監聽者模式的實現,其使用非常簡單。使用EventBus來實現監聽者模式,只需要三步操作。
- 通過注解@Subscribe來聲明事件回調方法;
- 調用EventBus的register方法來注冊監聽器;
- 通過post方法來觸發事件;
訂單邏輯通過EventBus事件總線來實現,大概是以下這個樣子:
public class OrderPage {
public static EventBus eventBus = new EventBus();
public void submitOrder() {
Integer userId = 1;
BigDecimal amount = BigDecimal.TEN;
eventBus.post(new PayEvent(userId, amount));
}
}
class PaymentService {
private MailService mailService;
@Subscribe
public void doPayment(PayEvent payEvent) {
// Do payment...
mailService.sendPaymentEmail(payEvent.getUserId(), payEvent.getAmount());
}
}
class UserService {
public String getEmailAddress(Integer userId) {
return "foo@bar.com";
}
@Subscribe
public void registerPayment(PayEvent payEvent) {
// Register payment in database...
}
}
class PayEvent {
private Integer userId;
private BigDecimal amount;
public PayEvent(Integer userId, BigDecimal amount) {
}
public Integer getUserId() {
return userId;
}
public BigDecimal getAmount() {
return amount;
}
}
public static void main(String[] args) {
PaymentService paymentService=new PaymentService();
UserService userService=new UserService();
OrderPage orderPage=new OrderPage();
orderPage.eventBus.register(paymentService);
orderPage.eventBus.register(userService);
}
要實現監聽者模式,時需要調用eventBus的register方法進行注冊,在需要處理事件的方法上使用@Subscribe注解。最后通過eventBus發布事件即可。使用事件總線,不需要定義特定的接口,不需要主動添加監聽器;
三、事件訂閱
EventBus通過register方法來注冊處理相應事件的類
public void register(Object object) {
Multimap<Class<?>, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
}
其核心是findAllSubscribers,找到實例中所有有Subscribe注解的方法並保存。返回的是一個Multimap < Class<?>,EventSubscriber>類型,其中Class是事件類型,EventSubsciber包含了類實例和具體處理事件的方法。Multimap保證了一種事件可以有多個監聽者來處理。
public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
EventSubscriber subscriber = makeSubscriber(listener, method);
methodsInListener.put(eventType, subscriber);
}
return methodsInListener;
}
發布事件
EventBus通過post方法來發布事件,首先通過事件類型找到需要處理的事件:事件本身以及其父類。根據事件類型從事件訂閱的緩存中取出處理該事件的訂閱者,並將其入隊。最后處理該隊列中的數據.
public void post(Object event) {
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
subscribersByTypeLock.readLock().lock();
try {
Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
if (!wrappers.isEmpty()) {
dispatched = true;
for (EventSubscriber wrapper : wrappers) {
enqueueEvent(event, wrapper);
}
}
} finally {
subscribersByTypeLock.readLock().unlock();
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
