设计模式(二) -- 解耦利器观察者模式

2,443 阅读5分钟

前言

在工作中为了使得自己的业务代码和同事的业务代码解耦开来,使用到了 SpringBoot 的事件机制。好奇其事件机制的实现,看了下源码发现就是使用了观察者模式,顺便做下总结。

目录

  • 基本概念
  • Java中的观察者模式
  • SpringBoot事件机制对于观察者模式的运用

基本概念

首先,什么是观察者模式:多个观察者去监听主题,当主题发生变化的时候,主题会通知所有的观察者。 盗用网上的一个图:

1

从上图的结构可以看出,主题维护了一个观察者类型的链表,每当主题变化的时候,就会循环调用(例如使用for循环)各个观察者的对应方法(这就是通知)。 在观察者模式中,又分为 推模型 和 拉模型

  • 推模型:主题向观察者推送详细信息。
  • 拉模型:主题把自身作为一个参数发送给观察者,观察者需要什么信息,那么就 主题.getXX() 。

好处:这样可以看到,被观察者不关心观察者是如何处理的,这样有利于解耦;另外,有时候观察者需要调用第三方进行支持,可能比较慢,此时也可以做成异步的。

Java中的观察者模式

再来看看 Java中的观察者模式,最后再提一下 个人在 SpringBoot 中对于观察者模式的实际使用。 Java 提供了 Observer接口(观察者接口) 和 Observable 接口(被观察者接口 / 主题接口)。源码如下:

Observable 接口(被观察者接口 / 主题接口):

public class Observable {  
    private boolean changed = false;  
    private Vector obs;  
    public Observable() {  
        obs = new Vector<>();  
    }  
    public synchronized void addObserver(Observer o) {  
        if (o == null)  
            throw new NullPointerException();  
        if (!obs.contains(o)) {  
            obs.addElement(o);  
        }  
    }  
    public synchronized void deleteObserver(Observer o) {  
        obs.removeElement(o);  
    }  
    public void notifyObservers() {  
        notifyObservers(null);  
    }  
    public void notifyObservers(Object arg) {  

        Object[] arrLocal;  

        synchronized (this) {  
            if (!changed)  
                return;  
            arrLocal = obs.toArray();  
           ·            clearChanged();  
        }  

        for (int i = arrLocal.length-1; i>=0; i--)  
            ((Observer)arrLocal[i]).update(this, arg);  
    }  

    public synchronized void deleteObservers() {  
        obs.removeAllElements();  
    }  
    protected synchronized void setChanged() {  
        changed = true;  
    }  
    protected synchronized void clearChanged() {  
        changed = false;  
    }  
    public synchronized boolean hasChanged() {  
        return changed;  
    }  
    public synchronized int countObservers() {  
        return obs.size();  
    }  
}  

如上代码:通过 Vector 维护一个 观察者类型的数组。通过调用 notifyObeservers(Object arg) 方法 来通过观察者。在实现中,也是通过for 循环 通知。 Ps:注意:从代码上看,需要先设changed。

Observer接口(观察者接口):

public interface Observer {  
    void update(Observable o, Object arg);  
} 

这两个参数的含义为:

* @param   o     the observable object.  
* @param   arg   an argument passed to the notifyObservers  

所以,此时即实现了 推模型,也实现了 拉模型。如果我们使用,那么分别实现这两个接口即可。

SpringBoot事件机制对于观察者模式的运用

那么在个人的实际运用中,做的是一个记账的服务,让别人来调用。当然,可以让别人直接在他们的业务处理后面,例如购买了XX东西,马上就直接调用我的记账服务,但是这样其实是一个紧耦合,由于是两个不同的业务,直接调用如果记账出错,那么主流程也跑不下去了,这样不行。那么 观察者模式就有利于解耦

做起来简单的几行代码就ok了,发送消息:

    private void publishTradingRecordEvent(TradingRecord tradingRecord) {

        TradingRecordEvent tradingRecordEvent = new TradingRecordEvent(tradingRecord);

        tradingRecordEvent.setTradingRecordId(tradingRecord.getId());
        tradingRecordEvent.setTradingUser(tradingRecord.getTradingUser());
        tradingRecordEvent.setAmount(tradingRecord.getAmount());
        tradingRecordEvent.setProductId(tradingRecord.getProductId());
        tradingRecordEvent.setProductRate(tradingRecord.getProduct().getRate());
        tradingRecordEvent.setEndDate(tradingRecord.getProduct().getEndDate());
        tradingRecordEvent.setTradingDate(tradingRecord.getTradingDate());
        tradingRecordEvent.setTradingType(tradingRecord.getTradingType());
        tradingRecordEvent.setTermType(tradingRecord.getProduct().isTermType());
        tradingRecordEvent.setSourceTradingRecordId(tradingRecord.getSourceInvestId());

        this.applicationEventPublisher.publishEvent(tradingRecordEvent);
    }

然后接受消息:

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,fallbackExecution = true)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handlerTradingRecord(TradingRecordEvent event) {
        ......
    }

这样,就能达到解耦的目的了,当然了,这里是用了注解的形式,而且结合的事务。不需要事务的话,可以对应的换成 @EventListener 或者实现 ApplicationListener 即可。

那么,这是怎么实现的呢。其实对于Spring Boot 的事件机制,同样离不开这2个东西-主题,观察者。spring boot 把之前所说的通知,包装成了一个 Event。下面分析这三者。

SpringBoot的主题

Spring boot 的主题 可以 由 ApplicationContext 来充当。ApplicaitonContext 继承于 ApplicationEventPublisher。所以说像上面的代码直接 这样子来发送了 this.applicationEventPublisher.publishEvent(tradingRecordEvent);,ApplicaiotnEventPublisher 源码如下:

public interface ApplicationEventPublisher {  

    /** 
     * Notify all listeners registered with this application of an application 
     * event. Events may be framework events (such as RequestHandledEvent) 
     * or application-specific events. 
     * @param event the event to publish 
     * @see org.springframework.web.context.support.RequestHandledEvent 
     */  
    void publishEvent(ApplicationEvent event);  

}  

其实该接口就是我们 发布事件的接口。具体的发送代码如下,在 SimpleApplicationEventMulticaster 类中:

@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeListener(listener, event);
                }
            });
        }
        else {
            invokeListener(listener, event);
        }
    }
}

SpringBoot 的观察者

Spring Boot 的观察者由 ApplicationListener 来进行充当,Spring 中实现 Listener 有两种方式,就像上面说的,继承 或者 注解。源码如下:

public interface ApplicationListener extends EventListener {  

    /** 
     * Handle an application event. 
     * @param event the event to respond to 
     */  
    void onApplicationEvent(E event);  

} 

可以看到, onApplicaiton(E event) 方法即 上文所说的 update 方法。 那么至于说 这个 listener 是什么时候注册到 主题(ApplicationEventPublisher)中的,这个作者没搞清楚。(Ps:如果大家知道,欢迎在 github 上留言,十分感谢)

SpringBoot的Event

上文所说的 主题 和 观察者 都有体现,传输的消息 Spring Boot 使用了一个 ApplicationEvent 进行了封装,源码如下:

public abstract class ApplicationEvent extends EventObject {  

    /** use serialVersionUID from Spring 1.2 for interoperability */  
    private static final long serialVersionUID = 7099057708183571937L;  

    /** System time when the event happened */  
    private final long timestamp;  

    public ApplicationEvent(Object source) {  
        super(source);  
        this.timestamp = System.currentTimeMillis();  
    }  

    public final long getTimestamp() {  
        return this.timestamp;  
    }  

}  

EventObject 源码:

public class EventObject implements java.io.Serializable {  

    private static final long serialVersionUID = 5516075349620653480L;  

    /** 
     * The object on which the Event initially occurred. 
     */  
    protected transient Object  source;  

    public EventObject(Object source) {  
        if (source == null)  
            throw new IllegalArgumentException("null source");  

        this.source = source;  
    }  

    public Object getSource() {  
        return source;  
    }  

    public String toString() {  
        return getClass().getName() + "[source=" + source + "]";  
    }  
}  

由上面的代码 可知,其实 ApplicationEvent 就是 把需要传输的消息 封装起来。这个消息并没有想 Java 的实现那样推拉模型都实现了,而是 *只实现了 拉模型 *

最后,我们程序中只需要 注入ApplicaitonContext 发送消息,实现 ApplicationListener 接口进行相应的处理即可。

总结

观察者模式实质是 有两个 东西:

主题中维护了 观察者列表的引用。当主题有变更的时候,循环调用观察者,通知其做相应的处理。另外,不论是 Java,还是 Spring ,都是利用这个原理,只是有不同的类充当 主题 和 观察者。 另外,观察者模式有一个好处:解耦

到这里,主要分析了下 Java 中是怎么实现观察者模式,以及工作中的事件机制的具体使用以及底层原理,有错误的地方请在 github 上指出 ,谢谢大家(评论功能暂时没时间做了)。

0    0