一、意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
二、适用性
《设计模式》中提到在以下任一情况下可以使用观察者模式:
1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
三、组成
——抽象主题(Subject)角色
把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者。一般用接口或抽象类来实现抽象主题角色。
——抽象观察者(Observer)角色
为具体的观察者定义一个更新接口,在得到主题的通知时更新自己。
——具体主题(Concrete Subject)角色
在具体主题内部状态改变时,给所有登记过的观察者发出通知。是抽象主题的子类(或实现)。
——具体观察者(Concrete Observer)角色
该角色实现抽象观察者角色所要求的更新接口,以便本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
四、结构
上述的Subject和Observer可以用java的interface或abstract class实现,在设计模式中“接口”并不一定指的就是java(或其他语言)中的interface。
有时我们或许并不需要在主题或观察者中加入记录本身状态的State属性,在观察者中或许也不需要保存一个Subject类型的指向主题的引用。观察者模式的重点在于:使用接口或抽象类实现具体主题与具体观察者的松耦合、主题通过维持Observer类型的集合与观察者实现一对多依赖、主题增加删除某观察者、主题改变时通知更新其所有的观察者。
五、实现
1.最简单的实现
本简单实现没有在主题和观察者中加入记录本身状态的属性,并且在观察者中也没有保存一个指向主题的引用,只是实现了观察者模式的基本特点,可以根据自身的需求对其扩展(比如:加入状态或指向主题的引用等)。
public interface Subject
{
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
此处使用接口而不是用抽象类实现抽象主题,是因为java(或C#等)是单继承的,当我们使用抽象类实现抽象主题时,那么某类想具有Subject的行为的同时又想具有另一超类的行为时,就会陷入两难的境地。
public class ConcreteSubject implements Subject
{
List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer o)
{
if (o == null)
throw new NullPointerException();
if (!observers.contains(o))
{
observers.add(o);
}
}
@Override
public void removeObserver(Observer o)
{
observers.remove(o);
}
@Override
public void notifyObservers()
{
for (Observer o : observers)
o.update();
}
}
(3)抽象观察者角色
public interface Observer
{
public void update();
}
(4)具体观察者角色
public class ConcreteObserver implements Observer
{
@Override
public 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.增强:加入主题状态,并在观察者中保存对主题的引用等
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这种单继承的语言,会出现我们前面所说的两难境地。
public interface Observer
{
public void update();
}
依然使用接口实现抽象观察者角色,主题与观察者的松耦合的就是体现在这里,对于java等单继承的语言,实现抽象观察者时,使用接口优于使用抽象类。
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);
}
@Override
public 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对观察者模式的内置支持。
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