天天看點

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

觀察者模式

概念其實很簡單,兩個主體,一個觀察者,一個被觀察者,當被觀察者發生變化時,觀察者會有相應的動作。舉幾個例子,和我們日常生活息息相關的紅綠燈,燈就相當于被觀察者,行人就相當于觀察者,當燈發生變化時,行人會有相應的動作:紅燈停,綠燈行,黃燈亮了等一等。再比如我們現在玩的公衆号,當我們訂閱了某個公衆号之後,公衆号每發表一篇文章,就會向訂閱了它的使用者發送這篇文章,我們就可以浏覽這篇文章了;當我們取消訂閱了,它就不會再向我們推送這篇文章了;隻要這個公衆号一直在運作,就會一直有人訂閱它或者取消訂閱。這兩個主體有個統一的稱呼:被觀察者成為主題(Subject),觀察者仍是稱為觀察者(Observer)。

觀察者模式還有很多其他的稱謂,如釋出-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀态上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。

理論上的東西講的再多也隻是停留在理論,下面我們來實作下,到底觀察者模式是個什麼神奇的東西。

類圖

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

所涉及到的角色如下:

抽象主題(Subject):提供接口,可以增加和剔除觀察者對象。一般用抽象類或者接口實作。      

抽象觀察者(Observer):提供接口,在得到主題的通知時更新自己。一般用抽象類或者接口實作。      

具體主題(ConcreteSubject):将有關狀态存入具體觀察者,在具體主題的内部狀态發生變化時,給所有注冊過的觀察者發出通知。一般是具體子類實作。      

具體觀察者(ConcreteObserver):存儲與主題的狀态自恰的狀态。具體觀察者角色實作抽象觀察者角色所要求的更新接口,以便使本身的狀态與主題的狀态 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用

在上述類圖中,ConcreteSubject中有一個存儲Observer的清單,這意味着ConcreteSubject并不需要知道引用了哪些ConcreteObserver,隻要實作(繼承)了Observer的對象都可以存到該清單中。在需要的時候調用Observer的update方法。

設計模式面試真題解析位址:設計模式面試題 14道

一般實作

Subject:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

Observer:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

ConcreteSubject:

package com.lee.myobserver.impl;

import com.lee.myobserver.Observer;
import com.lee.myobserver.Subject;

import java.util.ArrayList;
import java.util.List;

/**
 * 具體主題
 */
public class ConcreteSubject implements Subject {

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

    @Override
    public void attach(Observer observer) {
        this.observerList.add(observer);
        System.out.println("向ConcreteSubject注冊了一個觀察者");
    }

    @Override
    public void detach(Observer observer) {
        this.observerList.remove(observer);
        System.out.println("從ConcreteSubject移除了一個觀察者");
    }

    @Override
    public void notifyObservers() {
        this.observerList.forEach(observer -> observer.update(this.state));
    }

    public void changeState(String state) {
        this.state = state;
        this.notifyObservers();
    }
}           

複制

ConcreteObserver:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

MyObserverTest:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

完整代碼可以從spring-boot-test擷取,在com.lee.myobserver下

運作結果如下:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

以上實作中,我們發現ConcreteSubject必須維護一個Observer清單,這會讓人産生疑問:難道每個ConcreteSubject中的List<Observer>會有不同嗎?很明顯,不會,因為List儲存的類型是接口類型,那麼我們是不是可以把這個維護清單放到Subject中去了?還有我們發現attach、detach、notifyObservers在各個ConcreteSubject的實作都是一樣的,那麼我們是不是可以共用起來呢?答案是肯定的!那麼我們怎麼處理了,隻需要将Subject改成抽象類即可,類圖如下,具體實作就交給大家自己了,有了這個類圖,相信大家都可以輕松的完成代碼的實作。

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

jdk實作

在Java語言的java.util包下,提供了一個Observable類以及一個Observer接口,構成Java語言對觀察者模式的支援。

Observable:

package java.util;

/**
 * 抽象主題,用普通類實作
 */
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    
    /**
     * 建構一個含有0個觀察者的主題
     */
    public Observable() {
        obs = new Vector<>();
    }

    /**
     * 注冊某個觀察者到obs中
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 從obs中移除某個觀察者
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * 相當于notifyObservers(null),具體看下面那個
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 如果本對象有變化,則通知所有注冊了的觀察者,調用他們的update方法
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        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);
    }

    /**
     * 清空obs
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 将changed設定成true,标明本對象發生了變化
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 将changed重置成false
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 檢測本對象是否發生了變化
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * 傳回注冊的觀察者數量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}           

複制

Observer:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

Watched:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

Watcher:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

JdkObserverTest:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

完整代碼可以從spring-boot-test擷取,在com.lee.jdkobserver下

運作結果如下

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

類圖如下

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

jdk事件

JDK 1.0及更早版本的事件模型基于職責鍊模式,但是這種模型不适用于複雜的系統,是以在JDK 1.1及以後的各個版本中,事件處理模型采用基于觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java元件所引發的事件并不由引發事件的對象自己來負責處理,而是委派給獨立的事件處理對象負責。這并不是說事件模型是基于Observer和Observable的,事件模型與Observer和Observable沒有任何關系,Observer和Observable隻是觀察者模式的一種實作而已。

java中的事件機制的參與者有3種角色

Event Eource:事件源,發起事件的主體。

Event Object:事件狀态對象,傳遞的資訊載體,就好比Watcher的update方法的參數,可以是事件源本身,一般作為參數存在于listerner 的方法之中。

Event Listener:事件監聽器,當它監聽到event object産生的時候,它就調用相應的方法,進行處理。

其實還有個東西比較重要:事件環境,在這個環境中,可以添加事件監聽器,可以産生事件,可以觸發事件監聽器。

限于篇幅,具體案例實作就不講了,大家可以去spring-boot-test擷取,在com.lee.jdkevent下,裡面注釋寫的很詳細了,大家可以好好看看。

spring事件機制

springboot沒有自己的事件機制,用的就是spring的事件機制,這裡希望大家别以為和标題不一緻。springboot和spring的關系大家可以去捋一捋,這裡可以明确的告訴大家,不是對立關系!

spring的事件機制也是從java的事件機制拓展而來,具體往下看

ApplicationEvent:Spring中所有的事件父接口,繼承自java的EventObject

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
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;


    /**
     * Create a new ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.
     */
    public final long getTimestamp() {
        return this.timestamp;
    }

}           

複制

ApplicationListener:spring中所有的事件監聽器父接口,繼承自java的EventListener

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventListener;

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

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

}           

複制

具體案例

MessageEvent:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

MessageListener:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

MessageService:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

spring-event.xml:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

SpringEventTest:

同城旅行面試官:說說設計模式中事件底層原理!觀察者模式類圖一般實作jdk實作jdk事件spring事件機制具體案例總結觀察者模式優點與缺點觀察者模式應用場景事件機制

執行SpringEventTest中的main方法就可以看到結果了。

更多詳情請從spring-boot-test擷取,在com.lee.springevent下,大家可以根據注釋好好消化下。實在是不懂得話,可以放一下,下篇博文我會和大家一起着重研究下spring的事件源碼,後續的博文中也都會有提到。

總結

為什麼講與springboot啟動源碼無關的内容

關于前言中的疑問:為什麼講一篇與spring啟動源碼無關的内容,有兩個考慮,第一,确實是讓大家放松下心态,讀源碼确實挺累的;第二,主要目的,就是為spring-boot-2.0.3啟動源碼篇二 - SpringApplication的run方法(一)之

SpringApplicationRunListener做準備,裡面會涉及到spring的事件機制

觀察者模式優點與缺點

上面長篇大論,似乎也沒讨論其優點與缺點;其實通過實作大家應該才能體會到其優點與缺點,我總結下,有什麼不對的大家可以在評論區補充

優點:

(1) 主題與觀察者建立一個抽象的耦合而不是緊密的耦合,降低了耦合度;主題隻需要維護一個抽象觀察者的集合,無需了解具體觀察者,使得可以有各種各樣不同的觀察者實作。

(2) 支援廣播通信,主題會向所有已注冊的觀察者對象發送通知,簡化了一對多系統設計的難度。

(3) 符合“開閉原則”,增加新的具體觀察者無須修改原有代碼,可拓展性高。

缺點:

(1) 如果主題有很多直接或者間接觀察者,那麼全部通知到會很耗時。

(2) 主題與觀察者之間如果存在循環依賴,可能導緻系統崩潰。

觀察者模式應用場景

抽象的來講:對一個對象狀态的更新,需要其他對象同步更新,而且其他對象的數量動态可變;對象僅需要将自己的更新通知給其他對象而不需要知道其他對象的細節。大家可以根據以上兩點作為基本準則,某個場景是否滿足觀察者模式。

更多Java資料共享可以關注公衆号:麒麟改bug擷取!

具體應用場景就有很多了,比如文中的事件機制、公衆号訂閱,tomcat源碼中也有很多地方用到了(有興趣的兄弟可以去找找)。

事件機制

jdk事件實作是基于觀察者模式,而spring事件又是在jdk事件的基礎上進行了拓展。

主要有四個角色

事件源:觸發事件的主體,比如jdk事件案例中的UserService、spring事件案例中的MessageService、SpringBoot中的SpringApplication。

事件:事件本身,指的是EventObject中的source,具體可以是任何資料(包括事件源),用來傳遞資料,比如jdk事件案例中MessageEvent、spring事件案例中的MessageEvent。

事件監聽器:當事件發生時,負責對事件的處理,比如jdk事件案例中MessageListener、spring事件案例中的MessageListener。

事件環境:整個事件所處的上下文,對整個事件提供支援,類似web上下文;比如jdk事件案例中的UserService、spring事件案例中的ApplicationContext