您的位置:首页 > 编程语言 > Java开发

观察者模式(Observer Pattern,对象行为型模式,发布-订阅模式 Publish/Subscribe Pattern)

2017-04-18 08:31 801 查看

意图

通知状态变化

定义对象间的一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新,由抽象类或接口实现。

推模式:目标角色复杂,并且观察者角色进行更行时必须得到一些具体的变化信息

拉模式:目标角色简单

适用性

在以下任一情况下可以使用观察者模式:

1. 当一个抽象模型有两个方面,其中一个方面依赖于两一个方面。将这两者封装在独立对象中以使它们可以各自独立地改变和复用。

2. 当一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变

3. 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,比不希望这些对象是紧密耦合的。

结构



参与者

Subject

目标指导它的观察者。可以有任意多个观察者观察同一个目标。

提供注册和删除观察者对象的接口

ConcreteSubject

将有关状态存入各ConcreteObserver对象

当它的状态发生变化时,向它的各个观察者发出通知

Observer

1.为那些在目标发生改变时需要获得通知的对象定义一个更新接口,抽象观察者角色主要由抽象类或接口实现。

ConcreteObserver

维护一个指向ConcreteSubject对象的引用

存储有关状态,这些状态应与目标的状态保持一致

实现Observer的更新接口以使自身状态与目标的状态保持一致。

代码

Subject

public abstract class Subject {
private Vector<Observer> vector = new Vector<Observer>();

public void addObserver(Observer observer){
vector.add(observer);
}

public void deleteObserver(Observer observer){
vector.remove(observer);
}

public void notifyAllObserver(){
for(Observer observer:vector){
observer.update(this);
}
}

public abstract int getNumber();
public abstract void execute();
}


ConcreteSubject

public class ConcreteSubject extends Subject{
private Random random = new Random();
private int number;
public  int getNumber(){
return number;
}
public  void execute(){
for(int i=0;i<20;i++){
number = random.nextInt(50);
this.notifyAllObserver();
}
}
}


Observer

public interface Observer {
public void update(Subject subject);
}


ConcreteObserver

public class ConcreteObserver1 implements Observer{
public void update(Subject subject) {
System.out.println(subject.getNumber()+"======ConcreteObserver1========");
}
}
public class ConcreteObserver2 implements Observer{
public void update(Subject subject) {
System.out.println(subject.getNumber()+"======ConcreteObserver2========");
}
}


Main

public class Main {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer ob1 = new ConcreteObserver1();
Observer ob2 = new ConcreteObserver2();
subject.addObserver(ob1);
subject.addObserver(ob2);
subject.execute();
}
}


Java内部实现

Observer

/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author  Chris Warth
* @see     java.util.Observable
* @since   JDK1.0
*/
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);
}


Observable

/**
* This class represents an observable object, or "data"
* in the model-view paradigm. It can be subclassed to represent an
* object that the application wants to have observed.
* <p>
* An observable object can have one or more observers. An observer
* may be any object that implements interface <tt>Observer</tt>. After an
* observable instance changes, an application calling the
* <code>Observable</code>'s <code>notifyObservers</code> method
* causes all of its observers to be notified of the change by a call
* to their <code>update</code> method.
* <p>
* The order in which notifications will be delivered is unspecified.
* The default implementation provided in the Observable class will
* notify Observers in the order in which they registered interest, but
* subclasses may change this order, use no guaranteed order, deliver
* notifications on separate threads, or may guarantee that their
* subclass follows this order, as they choose.
* <p>
* Note that this notification mechanism has nothing to do with threads
* and is completely separate from the <tt>wait</tt> and <tt>notify</tt>
* mechanism of class <tt>Object</tt>.
* <p>
* When an observable object is newly created, its set of observers is
* empty. Two observers are considered the same if and only if the
* <tt>equals</tt> method returns true for them.
*
* @author  Chris Warth
* @see     java.util.Observable#notifyObservers()
* @see     java.util.Observable#notifyObservers(java.lang.Object)
* @see     java.util.Observer
* @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
* @since   JDK1.0
*/
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();
}
}


协作

当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。

在得到一个具体目标的改变通知后,ConcreteObserver对象可向目标对象查询信息。ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致。

效果

Observer模式允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。它也使你可以在不改动目标和其他观察者的前提下增加观察者。

目标和观察者间的抽象耦合

一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。

因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可以与一个处于较高层次的观察者通信不通知它,这样就保持了系统层次的完整性。如果目标和观察者混在一起,那么得到的对象要么贯穿两个层次(违反了层次性),要么必须放在这层次的某一个层中(这可能会损害层次抽象)。

支持广播通信

不像通常的请求,目标发送的通知不需要指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关系到底有多少对象对自己感兴趣;它唯一的责任就是通知它的各个观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。

意外的更新

因为一个观察者并不知道其他观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的操作可能会引起一系列观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。

简单的更新协议不提供具体细节说明目标中什么被改变了,这使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。

优点

1.观察者和被观察者之间是松耦合的,分别可以各自独立改变。

2. Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。

3. 遵守大部分GRASP原则和常用设计原则,高内聚、低耦合。

缺点

松耦合导致代码关系不明显,有时可能难以理解。

如果一个对象被大量观察者订阅的话,在广播通知的时候可能会有效率问题

实现

创建目标到其他观察者之间的映射

一个目标对象跟踪它应通知的观察者的最简单的方法是显式地在目标中保存对它们的引用。然而,当目标很多而观察者较少时,这样存储可能代价太高。一个解决办法是用时间换取空间,用一个关联查询机制(hash)来维护目标到观察者的映射。这样一个没有观察者的目标就不产生存储开销。但另一个方面,这一方法增加了访问观察者的开销。

观察多个目标

在某些情况下,一个观察者依赖于多个目标可能是有意义的。例如,一个表格对象可能依赖于多个数据源。在这种情况下,必须扩展Update接口以使观察者知道是哪一个目标送来的通知。目标对象可以简单地将自己作为Update操作的一个参数,让观察者知道应去检查哪一个目标。

谁触发更新

目标和它的观察者依赖于通知机制来保持一致。但是到底哪一个对象调用Notify来触发更新:

1. 由目标对象的状态设定操作在改变目标对象的状态后自动调用Notify。这种方法的优点是客户不需要记住要在目标对象上调用Notify,缺点是多个连续的操作会产生多次连续的更新,可能效率较低。

2. 让客户负责在适当的时候调用Notify。这样做的优点是客户可以在一系列的状态改变完成之后再一次性触发更新,避免了不必要的中间更新。缺点是给客户增加了触发更新的责任。由于客户可能会忘记调用Notify,这种方式较容易出差。

对已删除目标的悬挂引用

删除一个目标时应该注意不要在其观察者中遗留对该目标的悬挂引用。一种避免悬挂引用的方法是,当一个目标被删除时,让它通知它的观察者将对该目标的引用复位。一般来说,不能简单地删除观察者,因为其他的对象可能会引用它们,或者也可能它们还在观察其他的目标。

在发出通知前确保目标的状态自身是一致的

在发出通知前确保状态自身一致这一点很重要,因为观察者在更新其状态的过程中需要查询目标的当前状态。

当Subject的子类调用继承的该项操作时,很容易无意中违反这条自身一致性的准则。可以使用抽象的Subject类中的模板方法发送通知来避免这种错误。定义那些子类可以重定义的原语操作,并将Notify作为模板方法中的最后一个操作,这样当子类重新定义了Subject的操作时,还可以保证该对象的状态时自身一致的。

在文档中记录是哪一个Subject触发通知总是应该的。

避免特定于观察者的更新协议-推/拉模式

观察者模式的实现经常需要让目标广播关于其改变的其他一些信息。目标将这些信息作为Update操作一个参数传递出去。这些信息的量可能很小,也可能很大。

一个极端情况是,目标向观察者发送关于改变的详细信息,而不管它们需要与否。我们称之为推模型。另一个极端是拉模型;目标除最小通知外什么也不送出,而在此之后由观察者显式地向目标询问细节。

拉模型强调的是目标不知道它的观察者,而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的。另一个方面。拉模型可能效率较差,因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。

显式的指定感兴趣的改变

你可以扩展目标的注册接口,让各观察者注册为仅对特定事件感兴趣,以提高更新的效率。当一个事件发生时,目标仅通知那些已注册为对该事件感兴趣的观察者。支持这种做法一种途径是,对使用目标对象的方面的概念。

封装复杂的更新语义

当目标和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器(ChangeManager)。它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如,如果一个操作涉及到对几个相互依赖的目标进行改动,就必须保证仅在所有的目标都已更新完毕后,才一次性的通知它们的观察者,而不是每个目标都通知观察者。

ChangeManager的责任:

1. 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之必然。

2. 它定义一个特定的更新策略

3. 根据一个目标的请求,它更新所有依赖于这个目标的观察者。

ChangeManager是一个Mediator模式的实例。通常只有一个ChangeManager, 并且它是全局可见的。这里Singleton模式可能有用。

SimpleChangeManager总是更新每一个目标的所有观察者,比较简单。相反,DAGChangeManager处理目标及其观察者之间依赖关系构成的无环有向图。

经典例子

Java中已经定义、GUI、业务数据处理

相关模式

Mediator Pattern

Mediator Pattern在进行Mediator参与者和Colleague参与者通信时可能会用到观察者模式。Mediator Pattern和观察者模式在“通知状态变化“这点很相识,Pattern的目的和观点角度并不相同。

Mediator Pattern也会通知状态变化,不过充其量也只是Mediator Pattern的一部分,而Mediator Pattern的主要目的还是在Colleague参与者的协调。Observer Pattern则是放在Observer参与者(可能有多个)通知Subject参与者的状态,而且在通知后取得同步。

敬请期待“策略模式(Strategy Pattern,对象行为型模式)”
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息