观察者模式的基本概念可以参考观察者模式I,本节主要讨论java原生的观察者模式的源代码,并分析它的优缺点,最后将会讲解一种衍生出来的观察者模式,业内大多数开源框架为了解耦都或多或少演变于此思想(甚至并没有它灵活)
先行强调一下,它是一种思想,把它用到具体的场景需求下需要做特定的变动,比如考虑内存溢出问题,延时加载问题,因为这些问题不是我们讨论的重点,所以我将它都剥离了出去,仅仅展示他核心的部分
Observable和Observer
- 代码用例就不说了,大家对比第一篇文章,映射关系如下
- Observable -> Subject
- Observer -> Observer
Observable
- 首先先看一下
Observable.java
public class Observable {
List<Observer> observers = new ArrayList<Observer>();
boolean changed = false;
public Observable() {
}
public void addObserver(Observer observer) {
if (observer == null) {
throw new NullPointerException("observer == null");
}
//局部锁,加锁的原因见notifyObservers方法中注释
synchronized (this) {
if (!observers.contains(observer))
observers.add(observer);
}
}
protected void clearChanged() {
changed = false;
}
public int countObservers() {
return observers.size();
}
//方法锁,方法内部一条语句,性能和局部锁相同
public synchronized void deleteObserver(Observer observer) {
observers.remove(observer);
}
public synchronized void deleteObservers() {
observers.clear();
}
public boolean hasChanged() {
return changed;
}
public void notifyObservers() {
notifyObservers(null);
}
@SuppressWarnings("unchecked")
public void notifyObservers(Object data) {
int size = 0;
Observer[] arrays = null;
//加锁的原因是避免在通知的时候有新的通知者添加进来或者旧的通知者移除出去,造成少通知或者多通知的现象,用局部锁可以优化代码,提高性能
synchronized (this) {
if (hasChanged()) {
clearChanged();
size = observers.size();
arrays = new Observer[size];
//防御性编程机制,备忘录模式
observers.toArray(arrays);
}
}
if (arrays != null) {
for (Observer observer : arrays) {
observer.update(this, data);
}
}
}
protected void setChanged() {
changed = true;
}
}
- 上面有个函数单另拿出来说一下,这个函数可以对通知条件做更细致的判断,参见上面的notifyObservers方法,它在每次通知之前都需要调用hasChanged()方法,所以我们在notifyObservers之前必须先调用setChanged(),这其实起了一个缓冲的作用,我们可以自己覆盖这个方法,其中做一些额外的处理,在某种情况下返回true
protected void setChanged() {
changed = true;
}
- 注意notifyObservers()中的
observer.update(this, data);
,它说明了Observable会把自己的引用和相应的数据(data是Object类型)传递给观察者,增加程序的灵活性
Observer
- 非常简单,直接看代码,不用多说
public interface Observer {
void update(Observable observable, Object data);
}
Java原生观察者模式的缺点
- Observable作为一个类而不是接口,违反了面向接口编程的原则,灵活性会受到很大的影响
- Observable将关键的方法声明为了protected,这意味你在大多数情况下不能调用这些方法(protected作用域问题,不在一个包的情况下不能调用),所以这也违反了多用组合少用继承的设计原则
一种全新的观察者模式
先说一下我的想法,我认为,观察者模式的局限性在于无法做到1对1的通知,也就是说,无法特定的去通知某一个观察者,这样会造成极大的不灵活性,因为不是所有的观察者都希望得到通知,他们可能只在某些情况下得到通知,所以我的想法时用一个key去唯一标识某个观察者,让Subject去通知的时候根据key去通知,这样,就可以做到精细化的控制.下面就用代码给大家展现一下我的想法
代码目录结构
- main包作为测试使用
- observer和observerimpl作为观察者的接口和相应的实现类
- subject和subjectImpl作为相应的主题和主题的实现类
Subject和相应实现类
- 看一下
NewSubjectInterface.java
public interface NewSubjectInterface {
public void registerObserver(int key , NewObserverInterface value);
public void unregisterObserver(int key);
//根据key通知某个观察者,informations是传递的数据
public void notifyObserver(int key,Object informations);
//传递给所有的观察者
public void notifyObserver(Object informations);
}
注意key,先请记住,它是标识每一个观察者的标志,相当于身份证一样
- 然后看一下
NewSubjectInterface
的实现类NewSubjectImpl.java
package com.xd.subjectimpl;
import java.util.concurrent.ConcurrentHashMap;
import com.xd.observer.NewObserverInterface;
import com.xd.subject.NewSubjectInterface;
public class NewSubjectImpl implements NewSubjectInterface {
//使用这个数据结构的原因是防止在并发的情况下出现问题
private static final ConcurrentHashMap<Integer, NewObserverInterface> maps = new ConcurrentHashMap<>();
private NewSubjectImpl(){}
//静态内部类实现单例
public static class Instance{
public static NewSubjectImpl INSTANCE = new NewSubjectImpl();
}
public static NewSubjectImpl getInstance() {
return Instance.INSTANCE;
}
@Override
public void registerObserver(int key, NewObserverInterface value) {
maps.put(key, value);
}
@Override
public void unregisterObserver(int key) {
maps.remove(key);
}
//根据key去找到相应的观察者,然后向他发送信息
@Override
public void notifyObserver(int key, Object informations) {
if (maps.get(key) == null) {
return;
}
NewObserverInterface observer = maps.get(key);
observer.update(this, informations);
}
@Override
public void notifyObserver(Object informations) {
if (maps.size() == 0) {
return;
}
for (int key : maps.keySet()) {
notifyObserver(key, informations);
}
}
}
Observer和相应实现类
- 看一下
NewObserverInterface.java
类
public interface NewObserverInterface {
public void update(NewSubjectInterface newSubject , Object informations);
public int getkey();
}
- 看一下它的实现类,说明一下,打出名字来只是为了调式,看是否正确的调用了对应key的观察者,并没有其余的含义
public class NewObserverImpl implements NewObserverInterface {
private final String name;
private final int key;
public NewObserverImpl(String name,int key) {
this.name = name;
this.key = key;
}
@Override
public void update(NewSubjectInterface newSubject, Object informations) {
System.out.println(name);
}
@Override
public int getkey() {
return key;
}
}
测试
- 我们最后看一下它的测试结果,赋上代码
public class Main {
public static void main(String[] args) {
NewObserverInterface observer1 = new NewObserverImpl("xiaoA", 1);
NewObserverInterface observer2 = new NewObserverImpl("xiaoB", 2);
NewSubjectInterface subjectRegister = NewSubjectImpl.getInstance();
subjectRegister.registerObserver(observer1.getkey(), observer1);
subjectRegister.registerObserver(observer2.getkey(), observer2);
NewSubjectInterface subjectNotify = NewSubjectImpl.getInstance();
subjectNotify.notifyObserver(1, "haha");
}
}
- 以上的运行结果是
xiaoA
,如果你把这个1`subjectNotify.notifyObserver(1, "haha");
改为2,运行结果将是xiaoB
,如果你将subjectNotify.notifyObserver(1, "haha");
改为subjectNotify.notifyObserver("haha");
,运行结果将是
xiaoA
xiaoB
拓展
- 我为什么要把
NewSubjectImpl
作为单例,因为我考虑到了Android的使用场景,你在任意一个页面都可以获得这个单例,然后进行注册或者发送消息,这样极大的增加了程序的灵活性,你将会在页面之间传递数据如鱼得水,假设你想在A页面向D页面发送一个消息(注意这个页面不一定是一个Activity,可能是ViewPager的一个子页面等等),你只需要做以下几件事:
- 可以在D页面的static代码块中事先注册好
- 然后在A页面根据相应的key去发送消息就会调起B类中相应的方法