观察者模式(Observer Pattern)

一、观察者模式定义

观察者模式又称为发布订阅模式。一个发布者对应多个订阅者,一旦发布者的状态发生改变时,订阅者将收到订阅事件。

先看看一个生活中的例子:
我们使用简书想浏览Java相关的文章,于是我们点击订阅了[Java专题],当[Java专题]有新文章发布就会推送给我们,当然其他人也可以订阅[Java专题]并收到[Java专题]的推送。这就是观察者。 定义对象间的一对多关系,当一个对象的状态发生变化时,所依赖于它的对象都得到通知并主动更新。在观察者模式中,多个订阅者成为观察者(Observer),被观察的对象成为目标(Subject)。

实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见。(Java API 内置观察者模式用的是Observer接口与Observable类)

观察者模式UML图:


观察者模式UML图

二、用代码模拟读者订阅[Java专题]

先定义观察者模式的接口

2.1 目标接口Subject
/**
 * 目标接口
 * @author AC
 *
 */
public interface Subject {

    /**
     * 注册观察者
     */
    void registerObserver(Observer observer);
    
    /**
     * 取消注册观察者
     */
    void removeObserver(Observer observer);
    
    /**
     * 通知观察者更新数据
     */
    void notifyObservers();
}
2.2 观察者接口
/**
 * 观察者接口
 * @author AC
 *
 */
public interface Observer {

    /**
     * 被动推送的方式通知更新
     * @param obj
     */
    void updateByPush(Object obj);
    
    /**
     * 主动拉取数据的方式更新
     * @param subject
     */
    void updateByPull(Subject subject);
}
2.3 数据类:文章
/**
 * 数据类:文章Article
 * @author AC
 *
 */
public class Article {

    private String title;
    
    public Article(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}
2.4 被观察对象 :专题Topic
/**
 * 被观察对象 
 * @author AC
 * 实现了目标接口
 */
public class Topic implements Subject{
    
    private ArrayList<Observer> observers = new ArrayList<Observer>();
    
    private Article article;
    
    private String topciName;
    
    public Topic(String topciName) {
        this.topciName = topciName;
    }
    
    /**
     * 发布新文章 通知观察者(通知读者阅读)
     * @param article
     */
    public void publishNewArticle(Article article) {
        this.article = article;
        notifyObservers();
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        if(observers.indexOf(observer)>=0) {
            observers.remove(observer);
        }   
    }

    @Override
    public void notifyObservers() {
        for(Observer observer : observers) {
            observer.updateByPush(article);
            observer.updateByPull(this);
        }
    }

    public String getTopciName() {
        return topciName;
    }
    
    public Article getArticle() {
        return article;
    }
}
2.5 观察者:读者Reader
/**
 * 观察者:读者Reader
 * @author AC
 * 实现了观察者接口
 */
public class Reader implements Observer{
    
    private Subject subject;
    private String readerName;
    
    public Reader (String readerName) {
        this.readerName = readerName;
    }
    
    /**
     * 订阅专题
     */
    public void subscribe(Subject subject) {
        this.subject = subject;
        this.subject.registerObserver(this);
    }
    
    /**
     * 取消订阅
     */
    public void unsubscribe() {
        if(this.subject!=null) {
            this.subject.removeObserver(this);
        }
    }
    
    /**
     * 阅读被推送专题文章
     * @param article
     */
    public void readByPush(Article article) {
        System.out.println(readerName+"阅读被推送的文章:"+article.getTitle());
    }
    
    /**
     * 阅读主动去拉取专题文章
     * @param topic
     * @param article
     */
    public void readByPull(Topic topic,Article article) {
        System.out.println(readerName+"阅读主动去拉取专题 "+topic.getTopciName()+"的文章:"+article.getTitle());
    }
    

    @Override
    public void updateByPush(Object obj) {
        if(obj instanceof Article) {
            Article article = (Article)obj;
            readByPush(article);
        }
    }
    
    @Override
    public void updateByPull(Subject subject) {
        if(subject instanceof Topic) {
            Topic topic = (Topic)subject;
            //拿到被观察者对象后,可以主动“拉”取任何自己感兴趣的数据
            Article article = topic.getArticle();
            readByPull(topic,article);
        }
    }
}
2.6 客户端测试类 Client
public class Client {

    public static void main(String[] args) {
        Topic javaTopic = new Topic ("[Java专题]");
        
        Reader reader = new Reader("AC");
        reader.subscribe(javaTopic);
        
        Reader reader2 = new Reader("Alan Chen");
        reader2.subscribe(javaTopic);
        
        System.out.println("AC 和 Alan Chen 都订阅了 [Java专题], 他们都能收到更新推送...........");
        System.out.println();
        
        Article article = new Article("Java知识点");
        javaTopic.publishNewArticle(article);
        
     
        System.out.println();
        System.out.println();
      
        reader.unsubscribe();
       
        System.out.println("AC 取消了订阅了 [Java专题], 他收不到更新推送...........");
        System.out.println();
        
        Article article2 = new Article("Java高级进阶");
        javaTopic.publishNewArticle(article2);
    }
}
2.7 打印结果
AC 和 Alan Chen 都订阅了 [Java专题], 他们都能收到更新推送...........

AC阅读被推送的文章:Java知识点
AC阅读主动去拉取专题 [Java专题]的文章:Java知识点
Alan Chen阅读被推送的文章:Java知识点
Alan Chen阅读主动去拉取专题 [Java专题]的文章:Java知识点


AC 取消了订阅了 [Java专题], 他收不到更新推送...........

Alan Chen阅读被推送的文章:Java高级进阶
Alan Chen阅读主动去拉取专题 [Java专题]的文章:Java高级进阶

三、观察者的两种更新模式

在观察者模式的实现上,有推模式和拉模式两种方式。

  • 推模式:Subject主动向Observer推送消息,不管对方是否需要,推送的信息通常是目标对象的全部或部分数据,相当于广播通信。

  • 拉模型:Subject在通知Observer时只传递少量信息,如果观察者需要更具体的信息,再由Observer主动去拉取数据。这样的模型实现中会把Subject自身通过update方法传入到Observer。

上面例子中
void updateByPush(Object obj) 就是推模式;
void updateByPull(Subject subject)就是拉模式

四、Java API 内置观察者模式

java.util包内包含最基本的Observer接口与Observable类(其实对应的就是Subject类)
我们看一下Observer源码

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

我们看到update更新方法有两个参数:Observable、Object,可见Java API 内置观察者模式同时支持[拉]和[取]

我们再来看看Observable类源码

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector<>();
    }

    /**
     * Adds an observer to the set of observers for this object, provided
     * that it is not the same as some observer already in the set.
     * The order in which notifications will be delivered to multiple
     * observers is not specified. See the class comment.
     *
     * @param   o   an observer to be added.
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * Deletes an observer from the set of observers of this object.
     * Passing <CODE>null</CODE> to this method will have no effect.
     * @param   o   the observer to be deleted.
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to
     * indicate that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and <code>null</code>. In other
     * words, this method is equivalent to:
     * <blockquote><tt>
     * notifyObservers(null)</tt></blockquote>
     *
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to indicate
     * that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     *
     * @param   arg   any object.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

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

    /**
     * Clears the observer list so that this object no longer has any observers.
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * Indicates that this object has no longer changed, or that it has
     * already notified all of its observers of its most recent change,
     * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
     * This method is called automatically by the
     * <code>notifyObservers</code> methods.
     *
     * @see     java.util.Observable#notifyObservers()
     * @see     java.util.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * Tests if this object has changed.
     *
     * @return  <code>true</code> if and only if the <code>setChanged</code>
     *          method has been called more recently than the
     *          <code>clearChanged</code> method on this object;
     *          <code>false</code> otherwise.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#setChanged()
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

注意Observable是一个类,而不是接口,这有一定的局限性。因为如果某个类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。

五、用Java API 内置观察者模式再实现一次读者订阅[Java专题]

5.1 数据类:文章Article没有任何改变,先贴出来
/**
 * 数据类:文章
 * @author AC
 *
 */
public class Article {

    private String title;
    
    public Article(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}
5.2 被观察对象 :专栏Topic 现在改为继承Java内置的可观察者类Observable,因为Observable已经实现了addObserver、deleteObserver等方法,所以Topic不需要再实现了
/**
 * 被观察对象 
 * @author AC
 * 继承了可观察者类 同时也就拥有了Observable已实现的方法
 */
public class Topic extends Observable{
    
    private Article article;
    
    private String topciName;
    
    public Topic(String topciName) {
        this.topciName = topciName;
    }
    
    /**
     * 发布新文章 通知观察者(通知读者阅读)
     * @param article
     */
    public void publishNewArticle(Article article) {
        this.article = article;
        //要设置changed,观察者才能收到推送
        setChanged();
        
        //没有传更新的数据,观察者只能主动拉取
        //notifyObservers();
        
        //有传更新的数据,观察者可以被动接受推送,也可以主动拉取
        notifyObservers(article);
    }
    
    public String getTopciName() {
        return topciName;
    }
    
    public Article getArticle() {
        return article;
    }
}
5.3 观察者:读者Reader 现在改为实现Java内置的观察者接口Observer,并实现接口里唯一的一个update方法
/**
 * 观察者:读者Reader
 * @author AC
 * 实现了观察者接口 并实现了update方法
 */
public class Reader implements Observer{
    
    private Observable subject;
    private String readerName;
    
    public Reader (String readerName) {
        this.readerName = readerName;
    }
    
    /**
     * 订阅专栏
     */
    public void subscribe(Observable subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }
    
    /**
     * 取消订阅
     */
    public void unsubscribe() {
        if(this.subject!=null) {
            this.subject.deleteObserver(this);
        }
    }
    
    /**
     * 阅读被推送专题文章
     * @param article
     */
    public void readByPush(Article article) {
        System.out.println(readerName+"阅读被推送的文章:"+article.getTitle());
    }
    
    /**
     * 阅读主动去拉取专题文章
     * @param topic
     * @param article
     */
    public void readByPull(Topic topic,Article article) {
        System.out.println(readerName+"阅读主动去拉取专题 "+topic.getTopciName()+"的文章:"+article.getTitle());
    }
    
    
    /**
     * 实现update方法
     */
    @Override
    public void update(Observable o, Object arg) {
        
         //主动拉
         if(o instanceof Topic) {
             Topic topic = (Topic)o;
             //拿到被观察者对象后,可以主动“拉”取任何自己感兴趣的数据
             Article article = topic.getArticle();
             readByPull(topic,article);
         }
         
         //被动推
         if(arg instanceof Article) {
             Article article = (Article)arg;
             readByPush(article);
         }
    }
}
5.3 客户端测试类 Client 也没有任何变化
public class Client {

    public static void main(String[] args) {
        Topic javaTopic = new Topic ("[Java专题]");
        
        Reader reader = new Reader("AC");
        reader.subscribe(javaTopic);
        
        Reader reader2 = new Reader("Alan Chen");
        reader2.subscribe(javaTopic);
        
        System.out.println("AC 和 Alan Chen 都订阅了 [Java专题], 他们都能收到更新推送...........");
        System.out.println();
        
        Article article = new Article("Java知识点");
        javaTopic.publishNewArticle(article);
        
     
        System.out.println();
        System.out.println();
      
        reader.unsubscribe();
       
        System.out.println("AC 取消了订阅了 [Java专题], 他收不到更新推送...........");
        System.out.println();
        
        Article article2 = new Article("Java高级进阶");
        javaTopic.publishNewArticle(article2);
    }
}
5.4 测试结果也和上面测试的一样
AC 和 Alan Chen 都订阅了 [Java专题], 他们都能收到更新推送...........

Alan Chen阅读主动去拉取专题 [Java专题]的文章:Java知识点
Alan Chen阅读被推送的文章:Java知识点
AC阅读主动去拉取专题 [Java专题]的文章:Java知识点
AC阅读被推送的文章:Java知识点


AC 取消了订阅了 [Java专题], 他收不到更新推送...........

Alan Chen阅读主动去拉取专题 [Java专题]的文章:Java高级进阶
Alan Chen阅读被推送的文章:Java高级进阶

有点需要特别提一下的就是,Java API 内置的Observable需要调用一下 setChanged();观察者才能收到推送,我们看一下源码,发现notifyObservers方法里有判断changed的状态为true才去通知观察者。

public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

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

我们自己实现观察者模式的时候是没有这一点的,那加上这一个标志位有什么好处?好处就是更灵活,Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,因为每个业务中对变化的具体定义不一样,因此子类自己来判断是否变化;该变量既提供了一种抽象(变与不变),同时提供了一种观察者更新状态的可延迟加载,通过后面的notifyObservers方法分析可知观察者是否会调用update方法,依赖于changed变量,因此即使被观察者在逻辑上发生改变了,只要不调用setChanged,update是不会被调用的。如果我们在某些业务场景不需要频繁触发update,则可以适时调用setChanged方法来延迟刷新。

最后给大家送波福利

阿里云折扣快速入口

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容