观察者模式(Observer Pattern)(一):观察者模式介绍

来源:互联网 发布:java线程sleep时间 编辑:程序博客网 时间:2024/05/29 09:52

一、意图


定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。



二、适用性


《设计模式》中提到在以下任一情况下可以使用观察者模式:


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


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


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



三、组成


——抽象主题(Subject)角色

        把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者。一般用接口或抽象类来实现抽象主题角色。


——抽象观察者(Observer)角色

       为具体的观察者定义一个更新接口,在得到主题的通知时更新自己。


——具体主题(Concrete Subject)角色

        在具体主题内部状态改变时,给所有登记过的观察者发出通知。是抽象主题的子类(或实现)。


——具体观察者(Concrete Observer)角色

        该角色实现抽象观察者角色所要求的更新接口,以便本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。



四、结构


上述的Subject和Observer可以用java的interface或abstract class实现,在设计模式中“接口”并不一定指的就是java(或其他语言)中的interface。

有时我们或许并不需要在主题或观察者中加入记录本身状态的State属性,在观察者中或许也不需要保存一个Subject类型的指向主题的引用。观察者模式的重点在于:使用接口或抽象类实现具体主题与具体观察者的松耦合、主题通过维持Observer类型的集合与观察者实现一对多依赖、主题增加删除某观察者、主题改变时通知更新其所有的观察者。



五、实现


1.最简单的实现


本简单实现没有在主题和观察者中加入记录本身状态的属性,并且在观察者中也没有保存一个指向主题的引用,只是实现了观察者模式的基本特点,可以根据自身的需求对其扩展(比如:加入状态或指向主题的引用等)。


(1)抽象主题角色

public interface Subject{public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();}

此处使用接口而不是用抽象类实现抽象主题,是因为java(或C#等)是单继承的,当我们使用抽象类实现抽象主题时,那么某类想具有Subject的行为的同时又想具有另一超类的行为时,就会陷入两难的境地。


(2)具体主题角色

public class ConcreteSubject implements Subject{List<Observer> observers = new ArrayList<Observer>();@Overridepublic void registerObserver(Observer o){if (o == null)throw new NullPointerException();if (!observers.contains(o)){observers.add(o);}}@Overridepublic void removeObserver(Observer o){observers.remove(o);}@Overridepublic void notifyObservers(){for (Observer o : observers)o.update();}}

(3)抽象观察者角色

public interface Observer{public void update();}


(4)具体观察者角色

public class ConcreteObserver implements Observer{@Overridepublic void update(){  //加入hashCode以区别不同的对象      System.out.println(this.hashCode()+"  says: I'm notified !");}}

(5)客户端测试:

public class Client{public static void main(String[] args){Subject subject = new ConcreteSubject();Observer o1 = new ConcreteObserver();Observer o2 = new ConcreteObserver();Observer o3 = new ConcreteObserver();subject.registerObserver(o1);subject.registerObserver(o2);subject.registerObserver(o3);subject.registerObserver(o1);//测试重复注册subject.notifyObservers();}}


2.增强:加入主题状态,并在观察者中保存对主题的引用等


(1).抽象主题角色

public abstract class Subject{private List<Observer> observers = new ArrayList<Observer>();public void registerObserver(Observer o){if (o == null)throw new NullPointerException();// 避免同一个观察者注册多次if (!observers.contains(o)){observers.add(o);}}public void removeObserver(Observer o){observers.remove(o);}public void notifyObservers(){for (Observer o : observers){o.update();}}}

这里使用的是抽象类实现的抽象主题角色,因为registerObserver、removeObserver、notifyObservers是所有子类公共的部分,将它们实现在超类中是理所当然的,可以复用父类的代码,子类可以更简洁地实现具体主题。但对于java这种单继承的语言,会出现我们前面所说的两难境地。


(2).抽象观察者角色

public interface Observer{public void update();}

依然使用接口实现抽象观察者角色,主题与观察者的松耦合的就是体现在这里,对于java等单继承的语言,实现抽象观察者时,使用接口优于使用抽象类。


(3).具体主题角色

public class ConcreteSubject extends Subject{Object state;//具体主题本身的状态public Object getState(){return state;}public void setState(Object state){this.state = state;}}

(4)具体观察者角色

public class ConcreteObserver implements Observer{// 具体观察者内部维持一个ConcreteSubject类型的指向具体主题的引用ConcreteSubject subject;Object state;public ConcreteObserver(ConcreteSubject subject){                if(subject==null)//观察者不能监听null                    throw new NullPointerException();                this.subject = subject;this.subject.registerObserver(this);}        //解除对主题的依赖(注册)public void unRegister(){this.subject.removeObserver(this);}@Overridepublic void update(){state = this.subject.getState();// hashCode用于区别不同的观察者System.out.println(this.hashCode() + "I'm notified!!");}}

此处具体观察者中包含一个主题类型的引用,注意是ConcreteSubject类型的,我们希望它是Subject类型的,因为这样我们就可以使用多态让具体观察者先解除对原主题的注册(因为它内部只维持了一个主题引用而不是多个)再注册到不同类型的具体主题。但是在这里,很难办到,因为State状态信息是具体主题所有的,不同的具体主题有不同的状态信息。这样的设计符合以上结构图中的描述,在《设计模式》这本经典书籍中的结构图也是这样描述的,我想,观察者模式的松耦合体现在:所有观察者都实现了Observer接口使主题不必知道具体的观察者类,只需调用它的update()方法就行。

这里,可以稍进一步修改(或许这种修改没能改变原来的状况):

        将具体观察者中的持有的主题引用改为Subject类型,而在使用ConcreteSubject时进行类型判断及强制类型转换(貌似要加入不少的if语句),《HeadFirst设计模式》中的天气报告板示例中是这样做的,见HeadFirst中的气象站的实现中的类图或具体观察者。

       还有一种方式,以上我们同步主题和观察者的状态时,使用的是:“state = this.subject.getState();”,这称为“拉”数据,即观察者根据自己的需要从主题中获取数据。然而,或许“推”数据更好一些,即将主题的状态作为参数通过update(State)传送给观察者(不管你用不用,把数据都给你啦),同样,《HeadFirst设计模式》中的天气报告板示例中是这样做的,见HeadFirst中的气象站的实现中的类图或具体观察者,java内置的对观察者模式的支持中也提供了两种选择:推或拉,见下面的内容:java对观察者模式的内置支持。


(5)测试

public class Client{public static void main(String[] args){ConcreteSubject concreteSubject = new ConcreteSubject();ConcreteObserver o1 = new ConcreteObserver(concreteSubject);ConcreteObserver o2 = new ConcreteObserver(concreteSubject);concreteSubject.notifyObservers();o1.unRegister();o2.unRegister();concreteSubject.notifyObservers();}}




六、java对观察者模式的内置支持


java.util.Observable类充当观察者模式中的抽象主题角色(在这里可以将其称为“可观察者”)

java.util.Observer接口充当观察者模式中的抽象观察者角色(体现了“松耦合”)

java内置的对观察者模式的支持结构图为:



不足之处(摘自HeadFirst):java.util.Observable是一个抽象类,就像我们前面所提到的,因为java是单继承的,这使得某类不可能同时具有Observable和其他超类的行为,这限制了Observable的复用潜力(这也是在代码实现1中使用interface的原因)。另外,Observable将关键方法如setChanged()设置成protected,这意味着,:除非你继承Observable类,否则你无法创建Observable实例并组合到你自己的对象中来,这违反了“多用组合,少用继承”的原则。

(一个示例:《使用java内置的支持实现HeadFirst气象站



七、推、拉数据


“推(push)”数据指的是主题将状态信息(数据)作为参数通过update(State)方法(在Observer接口中定义)传给具体观察者,具体观察者再根据需要使用参数State中有用的信息进行同步更新。


“拉(pull)”数据指的是接到通知后,观察者根据需要从主题中提取自己需要的数据。


在java内置的对观察者模式的支持中,也提供了这两种状态(数据)的传递的方式:


java.util.Observer接口(即抽象观察者角色)中有且只有一个方法:void update(Observable o, Object arg),其中参数arg传递的就是主题(可观察者)的状态信息。


java.util.Observable类(可观察者,即我们的“抽象主题角色”)中方法notifyObservers()就是对“拉”数据的支持,方法notifyObservers(Object arg)就是对 “推”数据的支持。



八、设计原则


设计原则:为了交互对象之间的松耦合设计而努力


在观察者模式中,改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应付变化,是因为对象之间的互相依赖降到了最低。



九、其他


1.上述的主题和观察者中的状态(数据)State泛指主题(或观察者)中的状态信息,可以是一组数据,并不是只有一个Object类型的状态数据State。


2.根据自己的需要适当实现观察者模式,如:抽象主题的实现方式(接口或抽象类?)是否在主题或观察者中加入表示本身状态的属性、是否在观察者中加入一个主题类型的引用(这个引用的类型是抽象主题类型的还是具体主题类型的?)、选择传递数据的方式(推或拉?)以及是否采用类似Observable中setChanged()方法适当调整主题通知观察者的程度(是立即通知还是达到一定程度才通知还是..?)等等。


3.对于更加复杂的依赖关系的观察者模式,《设计模式》中进行了阐述,摘抄如下


封装复杂的更新语义:

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

ChangeManager有三个责任:

a) 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用, 反之亦然。
b) 它定义一个特定的更新策略。
c) 根据一个目标的请求, 它更新所有依赖于这个目标的观察者。

下页的框图描述了一个简单的基于ChangeManager的Observer模式的实现。有两种特殊的ChangeManager。SimpleChangeManager总是更新每一个目标的所有观察者, 比较简单。相反,DAGChangeManager处理目标及其观察者之间依赖关系构成的无环有向图。当一个观察者观察多个目标时, DAGChangeManager要比SimpleChangeManager更好一些。在这种情况下, 两个或更多个目标中产生的改变可能会产生冗余的更新。DAGChangeManager保证观察者仅接收一个更新。当然,当不存在多重更新的问题时, SimpleChangeManager更好一些。ChangeManager是一个Mediator(中介者)模式的实例。通常只有一个ChangeManager, 并且它是全局可见的。这里Singleton(单例)模式可能有用。


转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8871965