日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

三种方式实现观察者模式 及 Spring中的事件编程模型

發布時間:2025/7/14 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 三种方式实现观察者模式 及 Spring中的事件编程模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

觀察者模式可以說是眾多設計模式中,最容易理解的設計模式之一了,觀察者模式在Spring中也隨處可見,面試的時候,面試官可能會問,嘿,你既然讀過Spring源碼,那你說說Spring中運用的設計模式吧,你可以自信的告訴他,Spring中的ApplicationListener就運用了觀察者模式。

讓我們一步一步來,首先我們要知道到底什么是觀察者模式,用Java是如何實現的,在這里,我將會用三種方式來實現觀察者模式。

什么是觀察者模式

在現實生活中,觀察者模式處處可見,比如

  • 看新聞,只要新聞開始播放了,就會把新聞推送給訂閱了新聞的用戶,在這里,新聞就是【被觀察者】,而用戶就是【觀察者】。

  • 微信公眾號,如果一個用戶訂閱了某個公眾號,那么便會收到公眾號發來的消息,那么,公眾號就是【被觀察者】,而用戶就是【觀察者】。

  • 熱水器,假設熱水器由三部分組成,熱水器,警報器,顯示器,熱水器僅僅負責燒水,當水溫到達設定的溫度后,通知警報器,警報器發出警報,顯示器也需要訂閱熱水器的燒水事件,從而獲得水溫,并顯示。熱水器就是【被觀察者】,警報器,顯示器就是【觀察者】。

在這里,可以看到,【觀察者】已經失去自主的權利,只能被動的接收來自【被觀察者】的事件,無法主動觀察?!居^察者】成為了“受”,而【被觀察者】成為了“攻”?!颈挥^察者】只是通知【觀察者】,不關心【觀察者】收到通知后,會執行怎樣的動作。

而在設計模式中,又把【被觀察者】稱為【主題】。

在觀察者設計模式中,一般有四個角色:

  • 抽象主題角色(Subject)
  • 具體主題角色(ConcreteSubject)
  • 抽象觀察者角色(Observer)
  • 具體觀察者角色(ConcreteObserver)

其中,【主題】需要有一個列表字段,用來保存【觀察者】的引用,提供兩個方法(虛方法),即【刪除觀察者】【增加觀察者】,還需要提供一個給客戶端調用的方法,通知各個【觀察者】:你們關心(訂閱)的事件已經推送給你們了。

下面,我就用三種方式來實現觀察者模式。

經典

public class News {private String title;private String content;public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;} } 復制代碼

此類不屬于觀察者模式必須的類,用來存放事件的信息。

public interface Subject {List<People> peopleList = new ArrayList<>();default void add(People people) {peopleList.add(people);}default void remove(People people) {peopleList.remove(people);}void update(); } 復制代碼

抽象主題角色,在這個角色中,有一個字段peopleList,用來保存【觀察者】的引用,同時定義了兩個接口,這是Java8默認接口實現的寫法。這兩個接口是給客戶端調用的,用來【刪除觀察者】【增加觀察者】,還提供一個方法,此方法需要被【具體主題角色】重寫,用來通知各個【觀察者】。

public class NewsSubject implements Subject{public void update() {for (People people : peopleList) {News news = new News();news.setContent("今日在大街上,有人躲在草叢中襲擊路人,還大喊“德瑪西亞萬歲”");news.setTitle("德瑪西亞出現了");people.update(news);}} } 復制代碼

具體主題角色,重寫了【抽象主題角色】的方法,循環列表,通知各個【觀察者】。

public interface People {void update(News news); } 復制代碼

抽象觀察者角色,定義了一個接口,【具體觀察者角色】需要重寫這個方法。

下面就是【具體觀察者角色】了:

public class PeopleA implements People {@Overridepublic void update(News news) {System.out.println("這個新聞真好看");} } 復制代碼public class PeopleB implements People {@Overridepublic void update(News news) {System.out.println("這個新聞真無語");} } 復制代碼public class PeopleC implements People {@Overridepublic void update(News news) {System.out.println("這個新聞真逗");} } 復制代碼

客戶端:

public class Main {public static void main(String[] args) {Subject subject = new NewsSubject();subject.add(new PeopleA());subject.add(new PeopleB());subject.add(new PeopleC());subject.update();} } 復制代碼

運行:

我們學習設計模式,必須知道設計模式的優缺點,那么觀察者設計模式的優缺點是什么呢?

優點:

  • 【主題】和【觀察者】通過抽象,建立了一個松耦合的關系,【主題】只知道當前有哪些【觀察者】,并且發送通知,但是不知道【觀察者】具體會執行怎樣的動作。這也很好理解,比如 微信公眾號推送了一個消息過來,它不知道你會采取如何的動作,是 微笑的打開,還是憤怒的打開,或者是直接把消息刪了,又或者把手機扔到洗衣機洗刷刷。

  • 符合開閉原則,如果需要新增一個【觀察者】,只需要寫一個類去實現【抽象觀察者角色】即可,不需要改動原來的代碼。

缺點:

  • 客戶端必須知道所有的【觀察者】,并且進行【增加觀察者】和【刪除觀察者】的操作。

  • 如果有很多【觀察者】,那么所有的【觀察者】收到通知,可能需要花費很久時間。

當然以上優缺點,是最直觀的,可以很容易理解,并且體會到的。其他優缺點,可以自行百度。

Lambda

在介紹這種寫法之前,有必要介紹下***函數式接口***,函數式接口的概念由來已久,一般來說只定義了一個虛方法的接口就叫函數式接口,在Java8中,由于Lambda表達式的出現,讓函數式接口大放異彩。

我們僅僅需要修改客戶端的代碼就可以:

public static void main(String[] args) {Subject subject = new NewsSubject();subject.add(a -> System.out.println("已閱這新聞"));subject.add(a -> System.out.println("假的吧"));subject.add(a -> System.out.println("昨天就看過了"));subject.update();} 復制代碼

運行結果:

利用Lambda表達式和函數式接口,可以省去【具體觀察者角色】的定義,但是個人認為,這并非屬于嚴格意義上的觀察者模式,而且弊端很明顯:

  • 客戶端需要知道觀察者的具體實現。
  • 如果觀察者的具體實現比較復雜,可能代碼并沒有那么清晰。

所以這種寫法,具有一定的局限性。

借用大神的一句話

設計模式的出現,是為了彌補語言的缺陷。

正是由于語言的升級,讓某些設計模式發生了一定的變化,除了觀察者模式,還有模板方法模式、責任鏈模式等,都由于 Lambda表達式的出現,而出現了一些變化。

JDK

在Java中,本身就提供了一個接口:Observer,一個子類:Observable,其中Observer表示【觀察者】,Observable表示【主題】,可以利用這兩個子類和接口來實現觀察者模式:

public class NewsObservable extends Observable {public void update() {setChanged();notifyObservers();} } 復制代碼public class People1 implements Observer {@Overridepublic void update(Observable o, Object arg) {System.out.println("小編真無聊");} } 復制代碼public class People2 implements Observer {@Overridepublic void update(Observable o, Object arg) {System.out.println("開局一張圖,內容全靠編");} } 復制代碼

客戶端:

public static void main(String[] args) {NewsObservable newsObservable = new NewsObservable();newsObservable.addObserver(new People1());newsObservable.addObserver(new People2());newsObservable.update();} 復制代碼

運行結果:

在這里,我不打算詳細介紹這種實現方式,因為從Java9開始,Java已經不推薦這種寫法了,而推薦用消息隊列來實現。是不是很開心,找到一個借口不去研究Observable,Observer 這兩個東西了。

Spring中的事件編程模型

Spring中的事件編程模型就是觀察者模式的實現,SpringBoot就利用了Spring的事件編程模型來完成一些操作,這里暫時不表。

在Spring中定義了一個ApplicationListener接口,從名字就知道它是一個監聽器,是監聽Application的事件的,那么Application又是什么,就是ApplicationContext,ApplicationContext內置了幾個事件,其中比較容易理解的是:

  • ContextRefreshedEvent
  • ContextStartedEvent
  • ContextStoppedEvent
  • ContextClosedEvent 從名稱上來看,就知道這幾個事件是什么時候被觸發的了。

下面我演示下具體的用法,比如我想監聽ContextRefreshedEvent事件,如果事件發生了,就打印一句話。

@Component public class MyListener implements ApplicationListener{@Overridepublic void onApplicationEvent(ApplicationEvent applicationEvent) {if(applicationEvent instanceof ContextRefreshedEvent){System.out.println("刷新了");}} } 復制代碼@Configuration @ComponentScan public class AppConfig { } 復制代碼 public static void main(String[] args) {AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);} 復制代碼

運行結果:

當時學習Spring,看到Spring提供了各式各樣的接口來讓程序員們對Spring進行擴展,并且沒有任何侵入性,我不得不佩服Spring的開發者們。這里也是,我們可以看到在客戶端找不到任何關于“訂閱事件”的影子。

這種實現方式不是太好,可以看到我們在方法內部做了一個判斷:接收到的事件是否為ContextRefreshedEvent。

偉大的Spring還提供了泛型的ApplicationListener,我們可以通過泛型的ApplicationListener來完善上面的代碼:

@Component public class MyListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("刷新了");} } 復制代碼

我們還可以利用Spring中的事件編程模型來自定義事件,并且發布事件:

首先,我們需要定義一個事件,來實現ApplicationEvent接口,代表這是一個Application事件,其實上面所說的四個內置的事件也實現了ApplicationEvent接口:

public class MyEvent extends ApplicationEvent {public MyEvent(Object source) {super(source);} } 復制代碼

還需要定義一個監聽器,當然,在這里需要監聽MyEvent事件:

@Component public class MyListener implements ApplicationListener<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {System.out.println("我訂閱的事件已經到達");} } 復制代碼

現在有了事件,也有了監聽器,是不是還少了發布者,不然誰去發布事件呢?

@Component public class MyEventPublish implements ApplicationEventPublisherAware {private ApplicationEventPublisher publisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.publisher = applicationEventPublisher;}public void publish(Object obj) {this.publisher.publishEvent(obj);} } 復制代碼

發布者,需要實現ApplicationEventPublisherAware 接口,重寫publish方法,顧名思義,這就是發布方法,那么方法的參數obj是干嘛的呢,作為發布者,應該需要知道我要發布什么事件,以及事件來源(是誰觸發的)把,這個obj就是用來存放這樣的數據的,當然,這個參數需要我們手動傳入進去。setApplicationEventPublisher是Spring內部主動調用的,可以簡單的理解為初始化發布者。

現在就剩最后一個角色了,監聽器有了,發布者有了,事件也有了,對,沒錯,還少一個觸發者,畢竟要有觸發者去觸發事件啊:

@Component public class Service {@Autowiredprivate MyEventPublish publish;public void publish() {publish.publish(new MyEvent(this));} } 復制代碼

其中publish方法就是給客戶端調用的,用來觸發事件,可以很清楚的看到傳入了new MyEvent(this),這樣發布者就可以知道我要觸發什么事件和是誰觸發了事件。

當然,還需要把一切交給Spring管理:

@Configuration @ComponentScan public class AppConfig { } 復制代碼

客戶端:

public static void main(String[] args) {AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);context.getBean(Service.class).publish();;} 復制代碼

運行結果:

這一篇博客比較簡單,只是簡單的應用,但是只有會了應用,才能談源碼。

這篇博客到這里就結束了,謝謝大家。

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的三种方式实现观察者模式 及 Spring中的事件编程模型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。