观察者模式又称为发布-订阅(Publish/Subscribe)模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题的对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新自己。
说白了观察者模式就是解决“一动百动”的问题,即有一个主题对象发生了变化,对应的所有的观察对象都要发生变化。比如说我们的手机订阅了天气预报的功能,当天气预报更新的时候,所有的天气预报的订阅者都会受到天气预报的短消息。
观察者模式的结构图
package Observer;
import java.util.ArrayList;
/**
* Created by me on 2016/12/28.
* 抽象subject类
*/
abstract class Subject {
private ArrayList<Observer> observers = new ArrayList<Observer>();
//增加观察者
public void Attach(Observer observer){
observers.add(observer);
}
//移除观察者
public void Detach(Observer observer){
observers.remove(observer);
}
public void Notify(String state){
for(Observer observer:observers){
observer.Update(state);
}
}
}
package Observer;
/**
* Created by me on 2016/12/28.
* 抽象观察者
*/
abstract class Observer {
public abstract void Update(String state);
}
package Observer;
/**
* Created by me on 2016/12/28.
* 具体的subject类
*/
public class ConcreteSubject extends Subject {
//具体被观察者状态
private String state;
public void setState(String state){
this.state = state;
}
public String getState(){
return state;
}
}
package Observer;
/**
* Created by me on 2016/12/28.
* 具体的观察者
*/
public class ConcreteObserver extends Observer {
//观察者的状态
private String observerState;
@Override
public void Update(String state) {
observerState = state;
System.out.println("观察者状态为:" + observerState);
}
}
/**
* Created by me on 2016/12/28.
* 客户端
*/
public class Client {
public static void main(String[] args){
//创建主题对象
ConcreteSubject concreteSubject = new ConcreteSubject();
//创建观察者对象
Observer observer = new ConcreteObserver();
//将观察对象添加到主题对象
concreteSubject.Attach(observer);
//设置主题的状态
concreteSubject.setState("你好!!");
//通知观察者
concreteSubject.Notify(concreteSubject.getState());
}
}
由于JDK中为了方便开发人员,已经写好了现成的观察者接口和被观察者类,下面LZ先给出JDK中现成的观察者和被观察者代码,外加自己的一点解释,来帮助一些读者对JDK中对观察者模式的支持熟悉一下。
先看观察者接口
//观察者接口,每一个观察者都必须实现这个接口
public interface Observer {
//这个方法是观察者在观察对象产生变化时所做的响应动作,从中传入了观察的对象和一个预留参数
void update(Observable o, Object arg);
}
下面是被观察者类
import java.util.Vector;
//被观察者类
public class Observable {
//这是一个改变标识,来标记该被观察者有没有改变
private boolean changed = false;
//持有一个观察者列表
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);
}
//notifyObservers(Object arg)的重载方法
public void notifyObservers() {
notifyObservers(null);
}
//通知所有观察者,被观察者改变了,你可以执行你的update方法了。
public void notifyObservers(Object arg) {
//一个临时的数组,用于并发访问被观察者时,留住观察者列表的当前状态,这种处理方式其实也算是一种设计模式,即备忘录模式。
Object[] arrLocal;
//注意这个同步块,它表示在获取观察者列表时,该对象是被锁定的
//也就是说,在我获取到观察者列表之前,不允许其他线程改变观察者列表
synchronized (this) {
//如果没变化直接返回
if (!changed)
return;
//这里将当前的观察者列表放入临时数组
arrLocal = obs.toArray();
//将改变标识重新置回未改变
clearChanged();
}
//注意这个for循环没有在同步块,此时已经释放了被观察者的锁,其他线程可以改变观察者列表
//但是这并不影响我们当前进行的操作,因为我们已经将观察者列表复制到临时数组
//在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
//删除所有观察者
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//标识被观察者被改变过了
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();
}
}
被观察者除了一点同步的地方需要特殊解释一下,其余的相信各位都能看明白各个方法的用途。其实上述JDK的类是有漏洞的,或者说,在我们使用观察者模式时要注意一个问题,就是notifyObservers这个方法中的这一段代码。
for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
在循环遍历观察者让观察者做出响应时,JDK没有去抓取update方法中的异常,所以假设在这过程中有一个update方法抛出了异常,那么剩下还未通知的观察者就全都通知不到了,所以LZ个人比较疑惑这样的用意(LZ无法想象JAVA类库的制造者没考虑到这个问题),是sun当时真的忘了考虑这一点,还是另有它意?当然各位读者如果有自己的见解可以告知LZ,不过LZ认为,不管是sun如此做是别有用意,还是真的欠考虑,我们都要注意在update方法里一定要处理好异常,个人觉得JDK中比较保险的做法还是如下这样。
for (int i = arrLocal.length-1; i>=0; i--){ try { ((Observer)arrLocal[i]).update(this, arg); } catch (Throwable e) {e.printStackTrace();} }
这样无论其中任何一个update是否成功都不会影响其余的观察者进行更新状态,我们自己比较保险的做法就是给update方法整个加上try块,或者确认不会发生运行时异常。
观察者模式的优缺点
优点:
1、当两个对象之间送耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者模式提供了一种对象设计,让主题和观察者之间送耦合。主题所知道只是一个具体的观察者列表,每一个具体观察者都符合一个抽象观察者的接口。主题并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。
2、观察者模式支持“广播通信”。主题会向所有的观察者发出通知。
3、观察者模式符合“开闭原则”的要求。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进 行循环调用,可能导致系统崩溃。
3、 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式的适用场所
1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3、一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
参考:《大话设计模式》
http://blog.csdn.net/chenssy/article/details/8955696
http://www.cnblogs.com/zuoxiaolong/p/pattern7.html