文章目录
- 1. 前言
- 2. 观察者模式
- 2.1 源码
- 2.2 结构
- 3. Android中的观察者模式
1. 前言
观察者模式是在代码框架中使用比较高的一个设计模式,常常又叫做订阅/发布模式。而通过这个设计模式通常我们就可以做到代码的解耦。
在现实生活中,比如当我们订阅了Android
官方资讯后,用户就可以收到来自这些网站的推荐消息。在这个场景中就是发布/订阅模式。而这种观察行为通常是一个被观察者,多个观察者。通过观察者模式可以实现一种一对多的关系,使得当被观察者的状态发生改变的时候,所有的观察者都可以得到通知,并作出相应的更新操作。
2. 观察者模式
在Java
中Observer
和Observable
是JDK
内置实现的,当我们需要用到观察者模式的时候就可以考虑实现相关的方法。比如下面的案例。
首先定义一观察者,需要实现Observer
接口:
// 观察者
static class Person implements Observer{
private String name;
public Person(String n){
this.name = n;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(this.name+": ==> " + arg);
}
}
以及对应的被观察者对象,继承自Observable
类:
// 被观察者
static class Message extends Observable{
public void messageHasChanged(String content){
setChanged();
notifyObservers(content);
}
}
最后我们就可以简单的测试了:
public static void main(String[] args) {
Message message = new Message(); // 被观察者
Person person1 = new Person("张三"); // 观察者
Person person2 = new Person("李四");
Person person3 = new Person("王五");
// 将二者关联
message.addObserver(person1);
message.addObserver(person2);
message.addObserver(person3);
// 发布消息
message.messageHasChanged("这是一个测试消息!");
}
测试结果:
可以看到是按照注册的倒序来更新消息。当然至于为什么会是这样,其实在源码中是将原本的观察者对象拷贝到一个数组中,然后数组从最后一个位置向前遍历得来的。接下来不妨看看Observer
接口和Observable
类的源代码。
2.1 源码
Observer
接口非常简单,就一个未实现的方法update
:
public interface Observer {
void update(Observable o, Object arg);
}
Observable
类中定义了一个容器数组Vector
来存储观察者对象Observer
,在添加观察者对象的时候会对观察者做一个非空的判断。且定义了一个布尔类型的私有变量changed
来标识是否发生改变。整个代码逻辑比较简单,可以发现主要的逻辑在notifyObservers
方法中。在进行遍历通知观察者的时候,在该方法中首先对当前对象加了一个synchronized
锁,主要是为了判断当前被观察者对象是否改变,如果没有改变就返回。否则就将所有注册的观察者装载到一个Object
数组中(装载后将changed
变量重置为false
),然后逆序调用每个Observer
对象的update
方法。
public class Observable {
private boolean changed = false;
private Vector<Observer> 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);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
...
}
2.2 结构
为了直观的Idea
中查看到类图,不妨在一个包下面仿造上述两个类,然后再定义测试方法。最后使用Idea
的插件Sketch lt!
来进行plantuml
生成,最终看到结构。
定义为:
package com.weizu;
public interface CustomObserver {
void update(CustomObservable o, Object msg);
}
public class CustomObservable {
private List<CustomObserver> mObservers;
public CustomObservable(){
mObservers = new ArrayList<CustomObserver>();
}
public synchronized void addObserver(CustomObserver o) {
if (o == null)
throw new NullPointerException();
if (!mObservers.contains(o)) {
mObservers.add(o);
}
}
public void notifyObservers(Object msg) {
for (CustomObserver mObserver : mObservers) {
mObserver.update(this, msg);
}
}
}
测试用例:
public class Demo {
// 观察者
static class Person implements CustomObserver{
private String name;
public Person(String n){
this.name = n;
}
public void update(CustomObservable o, Object arg) {
System.out.println(this.name+": ==> " + arg);
}
}
// 被观察者
static class Message extends CustomObservable{
public void messageHasChanged(String content){
notifyObservers(content);
}
}
public static void main(String[] args) {
Message message = new Message(); // 被观察者
Person person1 = new Person("张三"); // 观察者
Person person2 = new Person("李四");
Person person3 = new Person("王五");
// 将二者关联
message.addObserver(person1);
message.addObserver(person2);
message.addObserver(person3);
// 发布消息
message.messageHasChanged("这是一个测试消息!");
}
}
然后使用下面的工具来生成weizu.plantuml
文件:
最后,拷贝weizu.plantuml
文件内容到http://www.plantuml.com/,即可看见下图:
不难发现其实上面的类图中涉及到了四种不同类型的角色,分别是被观察者、观察者、具体被观察者以及具体观察者对象。
在上面的代码中不难发现一件事情,那就是使用观察者模式确实可以降低代码的耦合关系。其主要思想不难看出主要是在被观察者中遍历观察者,后调用抽象方法update
。也就是说当观察者对象很多的时候,通知的发布就是一件非常耗时的事情了,可能回影响到程序的效率。
且在观察者和被观察者之间的关系也仅是抽象的调用关系,所以在这里依赖于抽象而没有依赖于具体的实现,符合依赖倒置原则。
3. Android中的观察者模式
在使用ListView
添加数据的时候,会调用Adapter
的notifyDataSetChanged
()方法。其实这里也是观察者模式的运用,不妨来简单看下源码。比如此时自定义的Adapter
继承自ArrayAdapter
,然后从这个自定义类的notifyDataSetChanged
方法开始追踪:
从上面的流程中可以清晰的看到,其实最终的观察者模式,且被观察者对象是Adapter
中的mDataSetObservable
对象,该对象继承自Observable
对象。从Observable
定义的mObservers
为泛型类型可以看出观察者的对象这里为了通用性,直接申明了泛型。
那么,被观察者是在什么地方进行观察者的注册的呢?在Adapter
中的观察者又是什么对象?带着这两个问题,这里继续看源码。
因为前面提到了,被观察这对象是Adapter
中的mDataSetObservable
对象,为了找到在什么地方注册的观察者,所以我们需要找到被观察者的注册方法,也就是:
public abstract class Observable<T> {
protected final ArrayList<T> mObservers = new ArrayList<T>();
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
...
}
那么,不妨按住Ctrl
+鼠标左键进行追踪:
可以看到前面我们看过的类BaseAdapter
中调用了这个方法,所以这里切换到这个方法中:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
...
}
使用类似的方法对类BaseAdapter
中的registerDataSetObserver
方法进行追踪,但其实没有结果了。所以这里尝试换个对象,因为适配器更新的对象为ListView
的每个Item
,按道理讲观察者对象应该就是这些Item
。所以这里找到ListView
的setAdapter
方法,这里因为我本地并没有下载相关的源码配置,所以这里androidxref来查看源码。比如ListView。
// ListView.java
public void setAdapter(ListAdapter adapter) {
// 如果有Adapter,且观察者存在就注销观察者
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
...
super.setAdapter(adapter);
if (mAdapter != null) {
...
// 创建观察者,并注册到被观察者对象
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
...
}
...
}
观察上面代码,我们知道其实观察者在这里也就是AdapterDataSetObserver
这个类的实例。不妨再次在网站上查找,这个类定义在AbsListView.java
中,为一个内部类:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
可以看到出现了前面在DataSetObservable
被观察者类中的遍历调用onChanged
方法,继续追踪很有意思,mFastScroll.onSectionsChanged()
方法如下:
// FastScroller.java
private Adapter mListAdapter;
public void onSectionsChanged() {
mListAdapter = null;
}
所以说其实主要的处理方法其实在其父类中,这里继续追踪父类AdapterView<ListAdapter>.AdapterDataSetObserver
中的onChanged()
:
也就是观察者调用的onChanged
方法即为其触发方法。在这个方法中,会请求重新绘制ListView
界面。
到这里是否就完了?明天继续!
References
- 《Android源码设计模式》