Java 中的观察者模式

2020-05-28 00:00:00 模式 订阅 接口 观察者 主题


----本文来源于Rohit Joshi的《Java Design Patterns》一书的Chapter7:Observer Design Pattern


1,前言

    

    因为SpringBoot的事件监听机制是基于观察者(发布订阅)模式,因此在弄清开始SpringBoot的事件监听机制的源码分析前,先来学习下观察者模式,嘿嘿。


2,观察者模式背景


这里以一个关于用户订阅体育赛事的例子为背景展开介绍观察者模式,这个例子大概是这样的:体育电台播放一些现场直播的体育赛事时,为了让一些注册的且优质的用户(注意:这里不是所有用户)能够不通过观看直播,但要实时通过短信收到现场直播的体育赛事的信息比如比分的实时更新,解说员的评论等,此时该如何来实现呢?


此时可以利用观察者模式来实现这个功能,这些优质用户可以订阅自己感兴趣的体育赛事,当现场直播有比分更新或解说员的评论更新时,此时可以通过短信发给订阅这个赛事的优质用户。同时优质用户也可以取消订阅,此时不会收到任何信息。


3,什么是观察者模式


观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。


下面来认识下观察者模式的四个参与者:


图1


•Subject,用于注册观察者。观察者使用此接口注册为观察者,并从观察者中移除自身。


•Observer,观察者接口定义了一个更新接口,观察者应被通知主题的更改。所有的观察者都需要实现观察者接口。这个接口有一个update()方法,当主题(Subject)的状态改变时调用它。


•ConcreteSubject,存储ConcreteObserver对象的兴趣状态。当状态改变时,它会向观察者发送通知。具体的主题总是实现主题接口。notifyObservers()方法用于在状态更改时更新所有当前的观察者。


•ConcreateObserver,维护对具体主题对象的引用,并实现Observer接口。每个观察者注册一个具体的主题来接收更新。


总结:看上面图1可以知道,观察者与主题之间是“你拥有我,我拥有你”的状态,因为他们之间互相引用。当主题发生变化时,此时通过Observer的update接口更新具体观察者的订阅状态;而具体观察者通过Subject的registerObserver()接口来注册自身。


4,自己实现观察者模式


现在我们就用代码来实现前面提到的体育爱好者订阅体育赛事信息的例子。


首先放上该代码的github地址

https://github.com/jinyue233/java-demo/tree/master/design-pattern/src/main/java/com/jinyue/observer


编写代码前,我们头脑中应该有一个类图,要新建什么类,类与类之间的关系是什么?就像建房子一样,得先搭好房屋的基本框架,然后再砌墙。此时的类图关系就是房屋的基本框架,然后我们根据这个类图去填充代码即可。


 图2

4.1 Subject接口


主题接口,定义了一些注册观察者和通知观察者等相关的接口



public interface Subject {    /**     *  subscribeObserver, which is used to subscribe observers or we can say register the observers so that if there is a change     * in the state of the subject, all these observers should get notified.     * @param observer     */    public void subscribeObserver(Observer observer);
/** * unSubscribeObserver, which is used to unsubscribe observers so that if there is a change in the state of the subject, this * unsubscribed observer should not get notified. * @param observer */ public void unSubscribeObserver(Observer observer);
/** * notifyObservers, this method notifies the registered observers when there is a change in the state of the subject. */ public void notifyObservers();
String subjectDetails();}

4.2 Observer接口


观察者接口,定义了update等接口,其中update接口就是当主题变化时,会被ConcreteSubject回调来通知观察者们。



public interface Observer {    /**     * update(String commentary), method is called by the subject on the observer in order to notify it, when there is a change in the     * state of the subject.     * @param commentary     */    void update(String commentary);
/** * subscribe(), method is used to subscribe itself with the subject. */ void subscribe();
/** * unsubscribe(), method is used to unsubscribe itself with the subject. */ void unSubcribe();}

4.3 Commentary接口


该接口定义了解说员用来评论的方法


public interface Commentary {    void setCommentary(String commentary);    String getCommentary();}

4.4 CommentaryObject


CommentaryObject即是ConcreteSubject,值得注意的是该类对Observer的具体实现类即SMSUser持有一个引用,因为要通过该引用对象来更新观察者们。


public class CommentaryObject implements Subject, Commentary {    private List<Observer> observerList = new ArrayList<Observer>();    private String commentary;
public CommentaryObject(String commentary) { this.commentary = commentary; } public void subscribeObserver(Observer observer) { observerList.add(observer); }
public void unSubscribeObserver(Observer observer) { observerList.remove(observer); }
public void notifyObservers() { for (Observer observer : observerList) { observer.update(getCommentary()); } }
public String subjectDetails() { return getCommentary(); }

public void setCommentary(String commentary) { this.commentary = commentary; // 这里只要一更新评论,那么则会立即通知订阅者 notifyObservers(); }
public String getCommentary() { return this.commentary; }}

4.5 SMSUser


SMSUser类即是ConcreteObserver,值得注意的是该类也持有了Subject接口的具体实现类即CommentaryObjec的引用,要通过该引用来注册自身来实现订阅某个主题。


public class SMSUser implements Observer {    private Subject subject;    private String commentary;    private String userInfo;
public SMSUser(Subject subject, String userInfo) { if(subject==null){ throw new IllegalArgumentException("No Publisher found."); } this.subject = subject; this.userInfo = userInfo; }
public void update(String commentary) { this.commentary = commentary; // 这里只要subject一发布事件,那么这里订阅者就会马上知道消息 display(); // System.out.println("Subscribed successfully."); }
public void subscribe() { System.out.println("Subscribing "+userInfo+" to "+subject.subjectDetails() + " ..."); subject.subscribeObserver(this); System.out.println("Subscribed successfully."); }
public void unSubcribe() { System.out.println("Unsubscribing "+userInfo+" to "+subject.subjectDetails()+" ..."); subject.unSubscribeObserver(this); System.out.println("Unsubscribed successfully."); }
public void display() { System.out.println("["+userInfo+"]: " + this.commentary); }}


4.6 ObserverTest


前面已经实现编码,那么现在就是来测试的时候了,直接上测试代码:


public class ObserverTest {
@Test public void testObserverPattern(){ // 新建一个足球比赛的主题 Subject subject = new CommentaryObject("Soccer Match [2014AUG24]");
// 新建一个名为“Adam Warner”的观察者 Observer observer = new SMSUser(subject, "Adam Warner [New York]"); // 订阅主题 observer.subscribe(); System.out.println();
// 新建一个名为“Tim Ronney”的观察者 Observer observer2 = new SMSUser(subject, "Tim Ronney [London]"); // 订阅主题 observer2.subscribe();
// 此时足球赛事解说开始评论,该评论会实时更新到以上名为为“Tim Ronney”和“Adam Warner”的观察者 Commentary cObject = ((Commentary)subject); cObject.setCommentary("Welcome to live Soccer match"); cObject.setCommentary("Current score 0-0"); System.out.println();
// 名为“Tim Ronney”的观察者取消订阅,那么接下来相关主题信息不会推送给他 observer2.unSubcribe(); System.out.println();
// 此时足球赛事解说又开始评论,该评论此时只会实时更新到名为“Tim Ronney”的观察者 cObject.setCommentary("It’s a goal!!"); cObject.setCommentary("Current score 1-0"); System.out.println();
// 此时又加入了一个名为Marrie的观察者 Observer observer3 = new SMSUser(subject, "Marrie [Paris]"); observer3.subscribe(); System.out.println(); // 此时足球赛事解说又开始评论,该评论此时会推送给名为“Adam Warner”和“Marrie”的观察者 cObject.setCommentary("It’s another goal!!"); cObject.setCommentary("Half-time score 2-0"); }}


下面是测试结果截图:


                                                         图3

    根据上面的测试结果图,可以看到观察者(订阅用户)可以实现订阅某个体育赛事,这样当解说员解说比赛比如有比分变化时,此时可以通过短信推送给观察者(订阅用户),而不用观察者主动去获取信息,观察者只是被动接收订阅赛况即可。当观察者取消订阅某个之前订阅的体育赛事,此时就不会推送给这个观察者了。


    因为接下来要分析SpringBoot的事件监听机制,而SpringBoot的事件监听机制就是基于观察者(发布订阅)模式实现的,是观察者模式的具体应用案例。因此,在学习前是很有必要学习下观察者模式的。这样才能做到理论(设计模式)联系实践(springboot的事件监听机制)。


5,观察者模式分析


下面的分析出自名为"java的架构师技术栈"作者的一文:23种设计模式之观察者模式,一文就能理解


分析:“观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。


因为spring的事件机制其实也是观察者模式的具体应用,而且spring的事件机制中的每个listener执行逻辑默认也是单线程同步阻塞执行的,因此若listener过多,逻辑执行时间过长的话,此时可能会导致spring容器启动时间过长。当然,spring也有异步执行listener的方式,这个是通过定义一个线程池taskExecutor来实现的,回头再看看具体是如何实现。


6,观察者模式应用举例


1,spring的事件机制

2,Spring Cloud Bus实现消息总线

3,jdk自定义了一套观察者模式的相关接口


注:本文源自Rohit Joshi的《Java Design Patterns》一书的Chapter 7:Observer Design Pattern,若有侵权,请联系我删除即可。


参考:


1,23种设计模式之观察者模式,一文就能理解

     https://baijiahao.baidu.com/s?id=1639044219412817957&wfr=spider&for=pc


2,Rohit Joshi的《Java Design Patterns》一书的Chapter 7:Observer Design Pattern





相关文章