一、观察者模式定义
观察者模式又称为发布订阅模式。一个发布者对应多个订阅者,一旦发布者的状态发生改变时,订阅者将收到订阅事件。
先看看一个生活中的例子:
我们使用简书想浏览Java相关的文章,于是我们点击订阅了[Java专题],当[Java专题]有新文章发布就会推送给我们,当然其他人也可以订阅[Java专题]并收到[Java专题]的推送。这就是观察者。 定义对象间的一对多关系,当一个对象的状态发生变化时,所依赖于它的对象都得到通知并主动更新。在观察者模式中,多个订阅者成为观察者(Observer),被观察的对象成为目标(Subject)。
实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见。(Java API 内置观察者模式用的是Observer接口与Observable类)
观察者模式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方法来延迟刷新。