在没有互联网的时代里,报纸是人们获取信息的一个非常重要的途径,很多人会定一份或者几份报纸。报社会保存订阅者的信息,每当出了新报纸时,立即送到订阅者手里。订阅者在这种关系中处于被动的状态,它依赖于报社的行为,只有新报纸出版了,它才有可能阅读其中的内容。在互联网时代,报社可以把它的新闻放到网上,订阅者通过RSS订阅,每当有新的新闻就推送给订阅者。问题是:报社如何把新闻推送给所有的订阅者?进一步抽象描述这个问题:当一个对象的状态发生改变时,如何让依赖于它的所有对象得到通知,并进行处理呢?
观察者模式是这个问题的合理解决方案。观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。使用观察者模式的解决思路:首先,报社应该持有一个订阅者的列表,允许随时添加订阅者,同时也可以删除订阅者;其次,每当新闻产生时,报社会把新闻发给每个订阅者,这样的话订阅者也需要提供了一个接受新闻的方法;报社调用每个订阅者的接受新闻的方法,这样报社的任务就完成了,至于订阅者怎么处理这些新闻,那就与报社无关了。在观察者模式中,订阅者就是观察者,报社就是目标,它被观察者观察,这也是观察者模式名字的由来。
结构
观察者模式有如下几个角色:
Subject:目标对象抽象,目标对象一般提供对观察者注册和退订的维护;当目标的状态发生改变时,目标负责所有注册的,有效的观察者(在观察者模式的变形中,有效的观察者是很重要的);
ConcreteSubject:具体的目标实现对象,用来维护目标状态,当目标对象的状态发生改变时,通知所有注册的、有效的观察者,让观察者提供相应的处理;
Observer:定义观察者接口,提供目标通知时对应的更新方法,这个更新方法进行相应的业务处理,可以在这个方法里回调目标对象,以获取目标对象的数据;
ConcreteObserver:观察者的具体实现对象,用来接收目标的通知,并进行相应的后续处理,比如更新自身的状态以保持与目标的相应状态一致。
目标的示意性代码:
/**
* 被观察的目标,它应该提供如下功能:
* 提供对观察者的注册和退订
* 当目标状态发生改变时,通知所有的观察者
* @author cxy
*/
public interface Subject {
//注册观察者
public void attach(Observer observer);
//取消观察者
public void dettach(Observer oserver);
//状态发生改变,通知所有有效的观察者
public void notifyObservers();
}
/**
* 具体的目标,实现了Subject接口
*/
public class ConcreteSubject implements Subject {
//持有注册的观察者
private List<Observer> observers = new ArrayList<Observer>();
private String content;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void dettach(Observer observer) {
if(observers.contains(observer))
observers.remove(observer);
}
@Override
public void notifyObservers() {
for(Observer observer : observers)
observer.update(this);//把自己提供给观察者,这是拉模型
}
//状态发生了改变
public void setContent(String content) {
this.content = content;
notifyObservers();
}
}
观察者的示意性代码:
/**
* 观察者接口,定义接收通知的处理方法
* @author cxy
*
*/
public interface Observer {
public void update(Subject subject);
}
public class ConcretObserver implements Observer {
private String state;
//接收通知的处理方法,一般需要更新自身的状态与目标的状态保持一致
public void update(Subject subject) {
state = ((concreteSubject)subject).xxx();
}
}
使用观察者模式解决新闻订阅问题,我们需要一个具体定义报社接口,它是目标对象的抽象,还应该有具体的报社,它是目标的具体实现;然后定义订阅者的接口,观察者对象的抽象,然后定义具体的订阅者,它是观察者的具体实现。
import java.util.ArrayList;
import java.util.List;
/**
* 报社接口,它应该提供如下功能:
* 提供对订阅者的注册和退订
* 当出版新报纸时,通知所有的订阅者
* @author cxy
*/
public interface NewsPaper {
//注册观察者
public void attach(Reader reader);
//取消观察者
public void dettach(Reader reader);
//状态发生改变,通知所有有效的观察者
public void notifyObservers();
}
/**
* 具体的报纸,实现NewsPaper接口
* @author cxy
*
*/
public class NewsPaperImp implements NewsPaper {
//持有注册的观察者
private List<Reader> readers = new ArrayList<Reader>();
private String content;
@Override
public void attach(Reader reader) {
readers.add(reader);
}
@Override
public void dettach(Reader reader) {
if(readers.contains(reader))
readers.remove(reader);
}
@Override
public void notifyObservers() {
for(Reader reader : readers)
reader.update(this);
}
//状态发生了改变
public void setContent(String content) {
this.content = content;
notifyObservers();
}
public String getContent() {
return this.content;
}
}
/**
* 订阅者接口,当报社出新报纸时,处理相关业务
*
*/
public interface Reader {
public void update(NewsPaper news);
}
/**
* 具体的订阅者,为了简单起见,仅仅是打印一下内容
*
*/
public class ReaderImp implements Reader {
private String name;
public ReaderImp(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//一般来说修改自己的状态和目标保持一直
public void update(NewsPaper news) {
System.out.println(name + "收到了报纸,本期内容是:" + ((NewsPaperImp)news).getContent());
}
}
演示代码:
public class Client {
public static void main(String[] args) {
NewsPaperImp news = new NewsPaperImp();
Reader one = new ReaderImp("张三");
Reader two = new ReaderImp("李四");
Reader three = new ReaderImp("王五");
//注册
news.attach(one);
news.attach(two);
news.attach(three);
news.setContent("观察者模式");
//退订
news.dettach(one);
news.setContent("有人退订");
}
}
目标与观察者之间的关系
目标与观察者之间是一对多的关系,如果只有一个观察者,这个时候就退化成了一对一的关系,这是一种特殊情况,也可以使用观察者模式。观察者和目标是单向的依赖,观察者依赖于目标,主动权完全掌握在目标手中,观察者只能被动的等待通知。
一个观察者其实是可以观察多于一个的目标,它可以接受来自多个目标的通知,在这种情况下,观察者应该为每一个目标定义不同的通知更新方法。如果仅仅定义一个update()方法,在update()方法内部区分不同的目标,这是不妥的,极不优雅,而且出错的概率很高。应该为不同的目标定义不同的处理方法,这样逻辑就比较清晰了。
public interface Observer {
//处理目标A的方法
public void updateForA(A a);
//处理目标B的方法
public void updateForB(B b);
//处理目标C的方法
public void updateForC(C c);
}
目标和观察者之间可能会相互观察,比如有两个类A和B,在一套逻辑下,A是目标,B是观察者;在另一套逻辑下,B是目标,A是观察者。这种情况有可能在实际的应用中出现,但是要特别小心处理死循环的情况:A的改变会引起B的变化,如果B的这种变化恰巧又能引起A的变化,那么就有可能进入死循环了。
有效的观察者
在一般的观察者模式中,一旦目标的状态发生了改变,它会一视同仁的对待所有的观察者,通知它们更新。但是在实际应用中,往往需要根据具体的情况去通知相应的观察者。比如对同一个事情,不同岗位上的人做出的反应是不同的,以公安局举例,一般的个人纠纷直接找民警就可以解决了,这个时候没必要通知局领导吧,但是如果发生了恶劣的连续绑架案件,那么就应该通知局领导了。在观察者模式中也是如此,有些状态的改变只需要通知一部分观察者,这个时候就应该在目标的通知方法里进行判断,以确定通知哪些通知者,我个人觉得这甚至都不能算是观察者模式的变形,这是普遍存在的问题。在这种情况下,需要通知的观察者就是有效观察者,必须通知这些观察者,其他的观察者就不应该通知它们,以免引起误更新。
//进行逻辑判断,确定是否需要通知
public void notifyObserver() {
for(Observer obs : observers) {
if(判断是否需要通知该观察者)
obs.update(this);
//其他处理
}
}
推模型和拉模型
推模型:目标知道观察者需要的数据,在通知观察者更新的时候,目标主动向观察者推送详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或者部分数据,相当于在广播通信。在新闻的例子中,目标知道观察者需要的就是新闻的详细信息,所以它可以在通知观察者更新时,直接把新闻信息作为参数传递过去;观察者获得新闻信息后,就可以直接使用了,如下所示:
//目标直接把新闻信息(content)作为参数传递给订阅者
@Override
public void notifyObservers() {
for(Reader reader : readers)
reader.update(content);
}
//订阅者可以直接使用新闻信息
public void update(String content) {
System.out.println(name + "收到了报纸,本期内容是:" + content);
}
拉模型:目标在通知观察者时,只传递少量的信息,如果观察者需要更多的信息,需要观察者主动去目标那里“拉”。一般情况下,目标把自身传递过去,观察者可以通过目标的引用来获取想要的信息了。如下所示:
//目标把自身传递过去
@Override
public void notifyObservers() {
for(Reader reader : readers)
reader.update(this);
}
//观察者需要什么数据,就通过目标引用主动去“拉”
public void update(NewsPaper news) {
System.out.println(name + "收到了报纸,本期内容是:" + ((NewsPaperImp)news).getContent());
}
在拉模型中,目标把自己传递出去了,这基本上是目标对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
总结
观察者模式的本质是:触发联动。当目标对象发生改变时,就会出发相应的通知,然后调用观察者相应的方法。而且这个联动还是动态的,可以随时添加和删除观察者。一定要理解观察者模式的本质,设计模式不是从来就有,也不是条条框框,我愿意把它理解为一种经验,一种大致正确的解决思路。
观察者模式的优点
(1)实现了观察者和目标之间的抽象耦合。通过定义观察者接口,目标只知观察者接口,不知具体的观察者,这样具体的观察者就与目标之间解耦了。
(2)动态联动。
(3)广播通信,目标通知的信息要对所有有效的观察者进行广播。
观察者模式的缺点:可能会引起无谓的操作。每次目标都对所有的观察者发通知,不管这个观察者是否需要,这有可能引起观察者的误更新,所以一定要区分对待观察者!
使用场景:
(1)一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化。
(2)在更新一个对象的时候,需要连带更新其他对象,而且还不知道到底有多少个这样的对象。
(3)一个对象必须通知其他对象,同时又想跟被通知的对象解耦,这个时候就可以使用观察者模式,通过抽象一个观察者接口,就可以不跟具体的被通知对象打交道了。
Java中的观察者模式
Java本身就包含了观察者模式,在java.util包里提供了观察者接口:Observer,它只有一个方法:void update(Observable o, Object arg);当目标发生变化时,会调用它的update方法,这跟上面自己实现的观察者接口是一致的。同时它还提供了一个具体的类Observable,它就是目标对象,摘取其源码一窥究竟:
public class Observable {
//保存目标的状态,标示目标是否改变
private boolean changed = false;
//保存观察者,竟然用Vector,真土
private Vector obs;
public Observable() {
obs = new Vector();
}
//注册观察者
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//删除观察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//删除所有观察者
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//如果目标发生了改变,需要调用此方法
//注意它是protected,专为子类而使用
protected synchronized void setChanged() {
changed = true;
}
//清楚修改标记
protected synchronized void clearChanged() {
changed = false;
}
//判断是否修改
public synchronized boolean hasChanged() {
return changed;
}
//返回观察者的数量
public synchronized int countObservers() {
return obs.size();
}
//通知所有观察者,可以看出它相当于notifyObservers(null)
public void notifyObservers() {
notifyObservers(null);
}
/**
* 在这里进行了同步,它获取了此对象的Monitor,在它执行这段代码时,会有两个问题:
* (1)在这时添加观察者是不行的,必须得等它执行完,也就是说一个新添加的观察者没有得到通知
* (2)在这时删除观察者也是不行的,必须得等它执行完,也就是说一个应该删除的观察者获得了通知
* 首先会判断是否发生了改变,如果发生了改变,则把所有的观察者读到一个数组里
* 并清楚改变标记,然后挨个通知所有的观察者
* 看了这段源码,感觉实现的真不咋的
* @param arg
*/
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
}
关于Observable的行为上面都有注释,我们可以发现Java默认的是拉模型,如果使用notifyObservers(),它会把目标本身传递过去,如果使用notifyObservers(arg),它同样会把目标本身传递过去。有一点需要注意:Observable在通知观察者之前,会先检查changed的状态,所以子类在调用notifyObservers()方法之前,必须要先调用setChanged()方法。如果继承了这个类,那就不能继承其他类了,这是一个限制,可以考虑使用内部类来继承此类。只能说这代码实现还真挺土的,竟然还用Vector,竟然没用泛型。感觉完全没必要使用Java提供了默认实现。
转载请注明:喻红叶《Java与模式-观察者模式》
分享到:
相关推荐
JAVA-设计模式-行为型模式-观察者模式
结合微信公众号讲解观察者模式,生动形象,关键是上手快啊
在Java中,观察者模式可以通过Java的内置接口`java.util.Observer`和`java.util.Observable`来实现。`Observer`接口代表观察者,而`Observable`类代表被观察的对象,也称为主题(Subject)。下面将详细介绍这两个...
在Java中,观察者模式通常通过`java.util.Observable`类和`java.util.Observer`接口来实现。在这个"java设计模式-观察者 小案例"中,我们可能会看到一个简单的模拟,雅典娜作为观察者,而其他角色(如圣斗士)则是被...
**观察者模式与Java事件处理** Java事件处理机制,如AWT和Swing中的事件监听,也是基于观察者模式。例如,按钮点击事件就是一个典型的例子,按钮(事件源)是观察目标,而监听按钮点击的事件处理器(事件监听器)...
4. 行为型模式:包括职责链模式、命令模式、解释器模式、迭代器模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。行为型模式关注于对象之间的交互和行为,帮助我们更好地管理复杂的...
在Java中,观察者模式通常通过Java内置的`java.util.Observable`类和`java.util.Observer`接口来实现。`Observable`类代表被观察的对象,它可以有多个观察者,而`Observer`接口则定义了观察者的通用行为。 首先,...
Java 观察者模式详解 观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。该模式适用于需要在对象间建立...
《Java与模式---闫宏》这本书是针对Java程序员深入理解设计模式的重要参考资料。设计模式是软件工程中的一个重要概念,它们代表了在特定上下文中解决常见问题的最佳实践。这本书结合了中国的道家思想,以一种独特的...
在Java中,`java.util.Observable` 和 `java.util.Observer` 类为实现观察者模式提供了基础支持。 1. **主题(Subject)**:主题是被观察的对象,它可以是抽象的或具体的。主题维护了一个观察者列表,并提供了添加...
观察者模式是一种行为设计模式,它允许你定义一个订阅机制,可以在对象事件发生时通知多个“观察”该对象的其他对象。这个模式的核心在于建立一种一对多的关系,当一个对象的状态改变时,所有依赖于它的对象都会得到...
代理模式(Proxy Pattern)、单例模式(Singleton Pattern)、工厂方法模式(...观察者模式(Observer Pattern)、责任链模式(Chain of Responsibility Pattern)、访问者模式(Visitor Pattern)、状态模式(State ...
观察者模式是设计模式中的一种行为模式,它在Java编程中有着广泛的应用。该模式的主要目的是定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式也被...
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段...
在Java中,`java.util.Observable`和`java.util.Observer`类提供了观察者模式的内置支持。而在其他语言中,如C#、Python等,也可以轻松实现观察者模式,通过事件或委托机制。 总结来说,观察者模式是一种重要的设计...
《Java与模式-清晰书签版》是一本深入探讨Java编程语言与设计模式结合应用的书籍。这本书旨在帮助读者理解如何在实际开发中有效地运用设计模式,提高代码的可读性、可维护性和复用性。书中内容涵盖了Java语言基础、...
《Java与模式-清晰书签版》是一份包含多种Java设计模式详解的资源包,旨在帮助开发者深入理解和应用设计模式。这份资源集成了多种格式的文档,包括详细的文本描述、图表解析以及实际代码示例,使得学习过程更加直观...
在Java中,观察者模式的实现通常基于Java的内置接口`java.util.Observer`和`java.util.Observable`。下面将详细解释观察者模式的概念、结构以及如何在Java中应用这个模式。 **观察者模式的核心概念:** 1. **主题...
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。 - 状态模式:允许对象在其内部状态改变时改变它的行为,看起来好像对象改变了它的...
在实际应用中,观察者模式不仅可以用于简单的状态变更通知,还可以与其他设计模式结合,如策略模式、工厂模式等,以应对更复杂的场景。同时,Java 8引入了流(Stream)和函数式编程概念,提供了更高级的事件处理方式...