觀察者模式——從JDK到Spring

文章收錄在 GitHub JavaKeeper ,N線互聯網開發必備技能兵器譜

在軟件系統中常常會有這樣的需求:若是一個對象的狀態發生改變,某些與它相關的對象也要隨之作出相應的變化。html

img

  • 微信公衆號,若是一個用戶訂閱了某個公衆號,那麼便會收到公衆號發來的消息,那麼,公衆號就是『被觀察者』,而用戶就是『觀察者』
  • 氣象站能夠將天天預測到的溫度、溼度、氣壓等以公告的形式發佈給各類第三方網站,若是天氣數據有更新,要可以實時的通知給第三方,這裏的氣象局就是『被觀察者』,第三方網站就是『觀察者』
  • MVC 模式中的模型與視圖的關係也屬於觀察與被觀察

觀察者模式是使用頻率較高的設計模式之一。java

觀察者模式包含觀察目標和觀察者兩類對象,一個目標能夠有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,全部的觀察者都將獲得通知。git

定義

觀察者模式(Observer Pattern): 定義對象間一種一對多的依賴關係,使得當每個對象改變狀態,則全部依賴於它的對象都會獲得通知並自動更新。 github

觀察者模式是一種對象行爲型模式設計模式

觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。安全

細究的話,發佈訂閱和觀察者有些不一樣,能夠理解成發佈訂閱模式屬於廣義上的觀察者模式。微信

img

角色

  • Subject(目標):被觀察者,它是指被觀察的對象。 從類圖中能夠看到,類中有一個用來存放觀察者對象的Vector 容器(之因此使用Vector而不使用List,是由於多線程操做時,Vector在是安全的,而List則是不安全的),這個Vector容器是被觀察者類的核心,另外還有三個方法:attach方法是向這個容器中添加觀察者對象;detach方法是從容器中移除觀察者對象;notify方法是依次調用觀察者對象的對應方法。這個角色能夠是接口,也能夠是抽象類或者具體的類,由於不少狀況下會與其餘的模式混用,因此使用抽象類的狀況比較多。
  • ConcreteSubject(具體目標):具體目標是目標類的子類,一般它包含常常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知。同時它還實現了在目標類中定義的抽象業務邏輯方法(若是有的話)。若是無須擴展目標類,則具體目標類能夠省略。
  • Observer(觀察者):觀察者將對觀察目標的改變作出反應,觀察者通常定義爲接口,該接口聲明瞭更新數據的方法 update(),所以又稱爲抽象觀察者
  • ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態須要和具體目標的狀態保持一致;它實現了在抽象觀察者 Observer 中定義的 update()方法。一般在實現時,能夠調用具體目標類的 attach() 方法將本身添加到目標類的集合中或經過 detach() 方法將本身從目標類的集合中刪除。

類圖

再記錄下 UML 類圖的注意事項,這裏個人 Subject 是抽象方法,因此用斜體,抽象方法也要用斜體,具體的各類箭頭意義,我以前也總結過《設計模式前傳——學設計模式前你要知道這些》(被網上各類帖子毒害過的本身,認真記錄~~~)。多線程

實例

一、定義觀察者接口app

interface Observer {
    public void update();
}

二、定義被觀察者測試

abstract class Subject {
    private Vector<Observer> obs = new Vector();

    public void addObserver(Observer obs){
        this.obs.add(obs);
    }
    public void delObserver(Observer obs){
        this.obs.remove(obs);
    }
    protected void notifyObserver(){
        for(Observer o: obs){
            o.update();
        }
    }
    public abstract void doSomething();
}

三、具體的被觀察者

class ConcreteSubject extends Subject {
    public void doSomething(){
        System.out.println("被觀察者事件發生改變");
        this.notifyObserver();
    }
}

四、具體的被觀察者

class ConcreteObserver1 implements Observer {
    public void update() {
        System.out.println("觀察者1收到信息,並進行處理");
    }
}
class ConcreteObserver2 implements Observer {
    public void update() {
        System.out.println("觀察者2收到信息,並進行處理");
    }
}

五、客戶端

public class Client {
    public static void main(String[] args){
        Subject sub = new ConcreteSubject();
        sub.addObserver(new ConcreteObserver1()); //添加觀察者1
        sub.addObserver(new ConcreteObserver2()); //添加觀察者2
        sub.doSomething();
    }
}

輸出

被觀察者事件發生改變
觀察者1收到信息,並進行處理
觀察者2收到信息,並進行處理

經過運行結果能夠看到,咱們只調用了 Subject 的方法,但同時兩個觀察者的相關方法都被調用了。仔細看一下代碼,其實很簡單,就是在 Subject 類中關聯一下 Observer 類,而且在 doSomething() 方法中遍歷一下 Observerupdate() 方法就好了。

優缺點

優勢

  • 下降了目標與觀察者之間的耦合關係,二者之間是抽象耦合關係
  • 目標與觀察者之間創建了一套觸發機制
  • 支持廣播通訊
  • 符合「開閉原則」的要求

缺點

  • 目標與觀察者之間的依賴關係並無徹底解除,並且有可能出現循環引用
  • 當觀察者對象不少時,通知的發佈會花費不少時間,影響程序的效率

應用

JDK中的觀察者模式

觀察者模式在 Java 語言中的地位很是重要。在 JDK 的 java.util 包中,提供了 Observable 類以及 Observer 接口,它們構成了 JDK 對觀察者模式的支持(能夠去查看下源碼,寫的比較嚴謹)。but,在 Java9 被棄用了。

Spring 中的觀察者模式

Spring 事件驅動模型也是觀察者模式很經典的應用。就是咱們常見的項目中最多見的事件監聽器。

1. Spring 中觀察者模式的四個角色

  1. 事件:ApplicationEvent 是全部事件對象的父類。ApplicationEvent 繼承自 jdk 的 EventObject, 全部的事件都須要繼承 ApplicationEvent, 而且經過 source 獲得事件源。

    Spring 也爲咱們提供了不少內置事件,ContextRefreshedEventContextStartedEventContextStoppedEventContextClosedEventRequestHandledEvent

  2. 事件監聽:ApplicationListener,也就是觀察者,繼承自 jdk 的 EventListener,該類中只有一個方法 onApplicationEvent。當監聽的事件發生後該方法會被執行。
  3. 事件源:ApplicationContextApplicationContext 是 Spring 中的核心容器,在事件監聽中 ApplicationContext 能夠做爲事件的發佈者,也就是事件源。由於 ApplicationContext 繼承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定義了事件發佈的方法:publishEvent(Object event)
  4. 事件管理:ApplicationEventMulticaster,用於事件監聽器的註冊和事件的廣播。監聽器的註冊就是經過它來實現的,它的做用是把 Applicationcontext 發佈的 Event 廣播給它的監聽器列表。

2. coding~~

一、定義事件

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
        System.out.println("my Event");
    }
}

二、實現事件監聽器

@Component
class MyListenerA implements ApplicationListener<MyEvent> {
    public void onApplicationEvent(MyEvent AyEvent) {
        System.out.println("ListenerA received");
    }
}

@Component
class MyListenerB implements ApplicationListener<MyEvent> {
    public void onApplicationEvent(MyEvent AyEvent) {
        System.out.println("ListenerB received");
    }
}

三、事件發佈者

@Component
public class MyPublisher implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
    
    public void publishEvent(ApplicationEvent event){
        System.out.println("publish event");
        applicationContext.publishEvent(event);
    }
}

四、測試,先用註解方式將 MyPublisher 注入 Spring

@Configuration
@ComponentScan
public class AppConfig {

    @Bean(name = "myPublisher")
    public MyPublisher myPublisher(){
        return new MyPublisher();
    }
}
public class Client {

    @Test
    public void main() {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyPublisher myPublisher = (MyPublisher) context.getBean("myPublisher");
        myPublisher.publishEvent(new MyEvent(this));
    }
}

五、輸出

my Event
publish event
ListenerA received
ListenerB received

瞎扯

設計模式真的只是一種設計思想,不須要非得有多個觀察者才能夠用觀察者模式,只有一個觀察者,我也要用。

再舉個栗子,我是作廣告投放的嘛(廣告投放的商品文件通常爲 xml),假如個人廣告位有些空閒流量,這我得利用起來呀,因此我就從淘寶客或者拼夕夕的多多客上經過開放的 API 獲取一些,這個時候我也能夠用觀察者模式,每次請求 10 萬條商品,我就生成一個新的商品文件,這個時候我也能夠用觀察者模式,獲取商品的類是被觀察者,寫商品文件的是觀察者,當商品夠10萬條了,就通知觀察者從新寫到一個新的文件。

大佬可能覺這麼實現有點費勁,不用設計模式也好,或者用消息隊列也好,其實都只是一種手段,選擇適合本身業務的,開心就好。

參考

https://design-patterns.readt...

https://www.cnblogs.com/jmcui...

相關文章
相關標籤/搜索