观察者模式又叫作(发布-订阅)模式,属于行为型模式的一种,它定义了一种一对多的关系,当多个观察者同时监听到被观察者出现的变化,就会作出对应的处理。
废话不多说,今天分享一下关于观察者模式相关的内容。废话不多说,接下来直接进入代码实例。
观察者接口:
/**
* 观察者接口
* @author lyr
* @date 2017年11月27日
*/
public interface Observer {
void update(Observable o);
}
被观察者:
public class ConcreateObserver1 implements Observer{
public void update(Observable o) {
System.out.println("观察者1发现"+o.getClass().getSimpleName()+"出现变化!");
System.out.println("观察者1作出回应...");
}
}
public class ConcreateObserver2 implements Observer{
public void update(Observable o) {
System.out.println("观察者2发现 "+o.getClass().getSimpleName()+"出现变化!");
System.out.println("观察者2作出回应...");
}
}
具体的操作:
public class Observable {
List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer o){
observers.add(o);
}
public void change(){
System.out.println("我是被观察者,我已经发生变化了!");
notifyObservers();
}
public void notifyObservers(){
for(Observer obs:observers){
obs.update(this);
}
}
}
测试
public class Client {
public static void main(String[] args) {
Observable obsable = new Observable();
obsable.addObserver(new ConcreateObserver1());
obsable.addObserver(new ConcreateObserver2());
obsable.change();
}
}
下面是测试结果
虽然上面的代码很low,不过足够说明问题。从上面的几步下来可以很清楚的看到,被观察者出现变化的时候,它会通知其它的观察者,当观察者捕获到被观察者发出的通知,就会根据各自的需要作出相应的处理。不过这里需要注意的是,这些类都是自定义的类,并不是JDK中的相关类,所以要注意区分。
还有一点的就是,观察者模式分离了观察者和被观察者的责任,让它们自己去维护各自的功能模块区域,从而提高系统的可重用性和可维护性。
Java内置的观察者模式
在java.util包里面,包含了基本的Observer接口和Observable抽象类,在使用方面除了上述实现的功能还有注册、删除等等,并且通知观察者的那些功能都已经内置了。
先来看下观察者接口:
//观察者接口,每一个观察者都必须实现这个接口
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();
}
}
在使用Java内置的观察者模式的时候还需要注意一个问题,就是notifyObservers这个方法中的一段代码:
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
你会发现当在update的过程中出现异常的时候,自此之后的其它观察者就接收不到通知信息了,这个问题我是在观看一位前辈介绍关于观察者模式的时候他提到的,我估计sun公司应该会考虑到这个问题,但是现在看来并没有对他进行处理。所以按照正常的情况下,我在发现这个问题的时候一般的都会用try...catch语句块包裹这个方法,也就是如下代码所示:
for (int i = arrLocal.length-1; i>=0; i--){
try {
((Observer)arrLocal[i]).update(this, arg);
} catch (Exception e) {
e.printStackTrace();
}
}
这样对它进行处理之后,如果存在问题,也不会影响后面的观察者进行更新的状态。
当然了,你说除此之外观察者模式没有其它的问题是不可能的,一般来说应该是设计上的问题,毕竟被观察者和观察者是一对多的关系,如果反过来就没法用了。而且,每一个观察者都要实现观察者接口,才能添加到被观察者的列表中。如果说一个观察者已经存在,而且没办法更改其代码,那么就没办法成为一个真正的观察者,不过这个问题可以通过适配器模式处理掉。
这次的分享就到这里吧,下一篇再见!