观察者模式
最近一直没有什么状态,无心学习,业余时间不能再宅在家里了,宅男都有神经病,整天对着手机说话,不出门,没朋友,我说的对吗?siri?博客不定期更新,爱看不看,反正也没几个人看。还有,男人都要像灭霸一样,即使是反派,也要光明磊落,说不害谁就不害谁,更何况是队友呢?
记录一下观察者模式,这个模式应该是项目中最可能用到的模式之一了,话不多说,进入正题。
什么是观察者模式,我自己理解,就是一个类管理着所有依赖于它的观察者类,并且它状态变化时会主动给这些依赖它的类发出通知。
下面给出观察者模式的类图。
我们根据UML图翻译成java代码,首先是观察者接口。
/**
* 观察者接口
* @author xiaodongdong
* @create 2018-05-16 17:02
**/
public interface Observer {
void update(Observable o);
}
接下来是具体的观察者。
/**
* 具体观察者1
* @author xiaodongdong
* @create 2018-05-16 17:04
**/
public class ConcreteObserver1 implements Observer {
@Override
public void update(Observable o) {
System.out.println("观察者1观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者1做出相应");
}
}
/**
* 具体观察者2
* @author xiaodongdong
* @create 2018-05-16 17:05
**/
public class ConcreteObserver2 implements Observer {
@Override
public void update(Observable o) {
System.out.println("观察者2观察到" + o.getClass().getSimpleName() + "发生变化");
System.out.println("观察者2做出相应");
}
}
接下来是被观察者,被观察者持有一个观察者的集合,一旦被观察者发生变化,会主动调用观察者的接口方法通知相关观察者。
import java.util.ArrayList;
import java.util.List;
/**
* 被观察者
* @author xiaodongdong
* @create 2018-05-16 17:06
**/
public class Observable {
List<Observer> observers = new ArrayList<Observer>();
public void addObserver(Observer o){
observers.add(o);
}
public void changed(){
System.out.println("我是被观察者,我已经发生变化了");
//通知观察自己的所有观察者
notifyObservers();
}
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
测试一下。
/**
* 测试
* @author xiaodongdong
* @create 2018-05-16 17:09
**/
public class Client {
public static void main(String[] args) throws Exception {
Observable observable = new Observable();
observable.addObserver(new ConcreteObserver1());
observable.addObserver(new ConcreteObserver2());
//被观察者状态改变
observable.changed();
}
}
打印结果如下。
其实观察者是被动接收通知的,是不是能作为观察者,能不能接收通知,完全是被观察者决定。
在网上看到一个不错的例子,写在这里。我们经常看的小说网站,都有这样的功能,就是读者可以订阅作者,作者一旦有什么新书发表,就要通知已经订阅他的读者,这是一个典型的观察者模式的应用场景。
JDK已经帮我们写好了观察者接口和被观察者类,LZ列举一下JDK的关键实现,熟悉一下JDK的源码。
首先是观察者接口。
/**
* 观察者接口,每一个观察者都必须实现这个接口
*/
public interface Observer {
//除了依赖被观察者,还提供了一个预留参数
void update(Observable o, Object arg);
}
再是被观察者类。
package java.util.Observable;
import java.util.Observer;
import java.util.Vector;
//被观察者类
public class Observable {
//用来表示被观察者有没有改变
private boolean changed = false;
//观察者列表
private Vector<java.util.Observer> obs;
public Observable() {
obs = new Vector<>();
}
//添加观察者,添加时会去重
public synchronized void addObserver(java.util.Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//删除观察者
public synchronized void deleteObserver(java.util.Observer o) {
obs.removeElement(o);
}
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();
}
}
看注释应该能理解的差不多,但是这块有不足的地方,就是通知所有观察的地方,
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
在循环遍历观察者时,JDK没有处理update可能抛出的异常,假设有一个update方法抛出异常,那么剩下的观察者就都得不到通知了。所以我觉得这块代码可以优化成这样。
for (int i = arrLocal.length-1; i>=0; i--){
try {
((Observer)arrLocal[i]).update(this, arg);
} catch (Throwable e) {
e.printStackTrace();
}
}
我们自己做的时候,需要保证读者类中的update方法不会抛异常。JDK的观察者模式源码差不多就长这样,我们用JDK的实现把上面那个读者订阅作者的例子完成。首先要搞明白这个例子里面谁是观察者,谁是被观察者,很明显,读者是观察者,作者是被观察者,除此之外,我们还需要一个管理器来帮我们管理作者的列表,我们逐一实现,首先是读者类。
import java.util.Observable;
import java.util.Observer;
/**
* 读者
* @author xiaodongdong
* @create 2018-05-17 15:55
**/
public class Reader implements Observer {
private String name;
public Reader(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
//关注作者
public void subscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).addObserver(this);
}
//取关作者
public void unsubscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
}
//作者发表新书时调用
@Override
public void update(Observable o, Object obj) {
if (o instanceof Writer) {
Writer writer = (Writer) o;
System.out.println(name+"得到通知====" + writer.getName() + "发布了新书《" + writer.getLastNovel() + "》");
}
}
}
下面是作者类。
//作者类,要继承自被观察者类
public class Writer extends Observable{
private String name;//作者的名称
private String lastNovel;//记录作者最新发布的小说
public Writer(String name) {
super();
this.name = name;
WriterManager.getInstance().add(this);
}
//作者发布新小说了,要通知所有关注自己的读者
public void addNovel(String novel) {
System.out.println(name + "发布了新书《" + novel + "》!");
lastNovel = novel;
setChanged();
notifyObservers();
}
public String getLastNovel() {
return lastNovel;
}
public String getName() {
return name;
}
}
作者管理器,关键作用无非是快速查找作者,组合Map实现。
import java.util.HashMap;
import java.util.Map;
/**
* 作者管理器
* @author xiaodongdong
* @create 2018-05-17 15:56
**/
public class WriterManager{
private Map<String, Writer> writerMap = new HashMap<String, Writer>();
//添加作者
public void add(Writer writer){
writerMap.put(writer.getName(), writer);
}
//根据作者姓名获取作者 可优化 作者名字可能重复
public Writer getWriter(String name){
return writerMap.get(name);
}
//单例
private WriterManager(){}
public static WriterManager getInstance(){
return WriterManagerInstance.instance;
}
private static class WriterManagerInstance{
private static WriterManager instance = new WriterManager();
}
}
测试一下。
/**
* 测试
* @author xiaodongdong
* @create 2018-05-17 16:05
**/
public class Client {
public static void main(String[] args) {
//假设四个读者,两个作者
Reader r1 = new Reader("读者1");
Reader r2 = new Reader("读者2");
Reader r3 = new Reader("读者3");
Reader r4 = new Reader("读者4");
Writer w1 = new Writer("作者1");
Writer w2 = new Writer("作者2");
//四人关注了作者1
r1.subscribe("作者1");
r2.subscribe("作者1");
r3.subscribe("作者1");
r4.subscribe("作者1");
//读者3和读者4还关注了作者2
r3.subscribe("作者2");
r4.subscribe("作者2");
//作者发布新书就会通知关注的读者
//作者1写了设计模式
w1.addNovel("设计模式");
//作者2写了JAVA编程思想
w2.addNovel("JAVA编程思想");
//读者1取关作者1
r1.unsubscribe("作者1");
//作者1再写书将不会通知读者1
w1.addNovel("观察者模式");
}
}
结果如下。
观察者模式差不多就这样,后续项目中遇到实际的例子,可以补充在后面。