定义
观察者模式(有时又被称为发布(
publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是
软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。
面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
观察者模式的组成
抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
程序实例
通过程序实例来说明观察者模式:
首先定义抽象的观察者:
//抽象观察者角色
public interface Watcher{
public void update(String str);
}
然后定义抽象的主题角色,即抽象的被观察者,在其中声明方法(添加、移除观察者,通知观察者):
//抽象主题角色,watched:被观察
public interface Watched{
public void addWatcher(Watcher watcher);
public void removeWatcher(Watcher watcher);
public void notifyWatchers(String str);
}
然后定义具体的观察者:
public class ConcreteWatcher implements Watcher{
@Override public void update(String str) {
System.out.println(str);
}
}
之后是具体的主题角色:
public class ConcreteWatched implements Watched{
// 存放观察者
private List<Watcher> list = new ArrayList<Watcher>();
@Override
public void addWatcher(Watcher watcher) {
list.add(watcher);
}
@Override
public void removeWatcher(Watcher watcher) {
list.remove(watcher);
}
@Override
public void notifyWatchers(String str) {
// 自动调用实际上是主题进行调用的
for (Watcher watcher : list) {
watcher.update(str);
}
}
}
编写测试类:
public class Test{
public static void main(String[] args) {
Watched girl = new ConcreteWatched();
Watcher watcher1 = new ConcreteWatcher();
Watcher watcher2 = new ConcreteWatcher();
Watcher watcher3 = new ConcreteWatcher();
girl.addWatcher(watcher1);
girl.addWatcher(watcher2);
girl.addWatcher(watcher3);
girl.notifyWatchers("开心");
}
}
Java中内置的观察者模式
Observable类
java.util.Observable
Observable类用于创建可以观测到你的程序中其他部分的子类。当这种子类的对象发生变化时,观测类被通知。
观测类必须实现定义了update()方法的Observer接口。
当一个观测程序被通知到一个被观测对象的改变时,update()方法被调用。
显然,Observable是一个抽象的主题对象。
一个被观测的对象必须服从下面的两个简单规则:
第一,如果它被改变了,它必须调用setChanged()方法。
第二,当它准备通知观测程序它的改变时,它必须调用notifyObservers()方法,这导致了在观测对象中对update()方法的调用。
注意:如果在调用notifyObservers()方法之前没有调用setChanged()方法,就不会有什么动作发生。
notifyObservers()方法中包含clearChanged()方法,将标志变量置回原值。
notifyObservers()方法采用的是从后向前的遍历方式,即最后加入的观察者最先被调用update()方法。
Observer接口
java.util.Observer
此接口中只有一个方法:
void update(Observable o, Object arg)
这个方法在被观察对象(Observable类)的notifyObservers()方法中被调用。
程序例子
定义一个主题对象进行倒数计数,数字每次改变时,它的观察者收到这个数字。
一个观察者每次收到通知后打印出数字,另一个观察者在数字小于等于5时才开始打印。
class WatchedCounter extends Observable{
public void countdown(int number) {
for (; number >= 0; --number) {
// 设置改变变量
setChanged();
// 通知所有观察者,将number作为参数信息传递给观察者
notifyObservers(number);
}
}
}
class Watcher1 implements Observer{
@Override
public void update(Observable arg0, Object arg1) {
System.out.println("Watcher1's number: " + arg1);
}
}
class Watcher2 implements Observer{
@Override
public void update(Observable arg0, Object arg1) {
if (((Integer) arg1).intValue() <= 5) {
System.out.println("Watcher2's number: " + arg1);
}
}
}
public class ObserverTest{
public static void main(String[] args) {
WatchedCounter watchedCounter = new WatchedCounter();
Watcher1 watcher1 = new Watcher1();
Watcher2 watcher2 = new Watcher2();
//添加观察者
watchedCounter.addObserver(watcher1);
watchedCounter.addObserver(watcher2);
//开始倒数计数
watchedCounter.countdown(10);
}
}
接下来我们看看观察者模式在android中的应用。我们从最简单的开始。还记得我们为一个Button设置点击事件的代码吗。
Button btn=new Button(this);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("TAG","click");
}
});
其实严格意义上讲,这个最多算是回调,但是我们可以将其看成是一对一的观察者模式,即只有一个观察者。
其实只要是set系列的设置监听器的方法最多都只能算回调,但是有一些监听器式add进去的,这种就是观察者模式了,比如RecyclerView中的addOnScrollListener方法
private List<OnScrollListener> mScrollListeners;
public void addOnScrollListener(OnScrollListener listener) {
if (mScrollListeners == null) {
mScrollListeners = new ArrayList<OnScrollListener>();
}
mScrollListeners.add(listener);
}
public void removeOnScrollListener(OnScrollListener listener) {
if (mScrollListeners != null) {
mScrollListeners.remove(listener);
}
}
public void clearOnScrollListeners() {
if (mScrollListeners != null) {
mScrollListeners.clear();
}
}
然后有滚动事件时便会触发观察者进行方法回调
public abstract static class OnScrollListener {
public void onScrollStateChanged(RecyclerView recyclerView, int newState){
}
public void onScrolled(RecyclerView recyclerView, int dx, int dy){
}
}
void dispatchOnScrolled(int hresult, int vresult) {
//...
if (mScrollListeners != null) {
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrolled(this, hresult, vresult);
}
}
}
void dispatchOnScrollStateChanged(int state) {
//...
if (mScrollListeners != null) {
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrollStateChanged(this, state);
}
}
}
类似的方法很多很多,都是add监听器系列的方法,这里也不再举例。
还有一个地方就是Android的广播机制,其本质也是观察者模式,这里为了简单方便,直接拿本地广播的代码说明,即LocalBroadcastManager。
我们平时使用本地广播主要就是下面四个方法
LocalBroadcastManager
localBroadcastManager=LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
localBroadcastManager.unregisterReceiver(BroadcastReceiver receiver);
localBroadcastManager.sendBroadcast(Intent intent)
调用registerReceiver方法注册广播,调用unregisterReceiver方法取消注册,之后直接使用sendBroadcast发送广播,发送广播之后,注册的广播会收到对应的广播信息,这就是典型的观察者模式。具体的源代码这里也不贴。
android系统中的观察者模式还有很多很多,有兴趣的自己去挖掘,接下来我们看一下一些开源框架中的观察者模式。一说到开源框架,你首先想到的应该是EventBus。没错,EventBus也是基于观察者模式的。
观察者模式的三个典型方法它都具有,即注册,取消注册,发送事件
EventBus.getDefault().register(Object subscriber);
EventBus.getDefault().unregister(Object subscriber);
EventBus.getDefault().post(Object event);
接下来看一下重量级的库,它就是RxJava,由于学习曲线的陡峭,这个库让很多人望而止步。
创建一个被观察者
Observable<String> myObservable = Observable.create( new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> sub) {
sub.onNext("Hello, world!"); sub.onCompleted();
}
} );
创建一个观察者,也就是订阅者
Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) {
System.out.println(s);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
};
观察者进行事件的订阅
myObservable.subscribe(mySubscriber);
再比如当做下载的时候如何把状态更新到用户界面,就可以把下载类设置为被观察者,要显示的界面相应的设置为观察者
public class DownLoadManager {
// 七种状态
public static final int STATE_UNDOWNLOAD = 0; // 未下载
public static final int STATE_DOWNLOADING = 1; // 下载中
public static final int STATE_PAUSEDOWNLOAD = 2; // 暂停下载
public static final int STATE_WAITINGDOWNLOAD = 3; // 等待下载
public static final int STATE_DOWNLOADFAILED = 4; // 下载失败
public static final int STATE_DOWNLOADED = 5; // 下载完成
public static final int STATE_INSTALLED = 6; // 已安装
// 得打本类实例的方法
public static DownLoadManager instance;
// 用hashMap记录正在下载的一些downLoadInfo,因为一个包对应于一个下载信息,所以用map的形式
Map<String, DownLoadInfo> mDownLoadInfoMaps = new HashMap<String, DownLoadInfo>();
// 得到本类的实例化方法
public static DownLoadManager getInstance() {
if (instance == null) {
synchronized (DownLoadManager.class) {
if (instance == null) {
instance = new DownLoadManager();
}
}
}
return instance;
}
/** 用户点击了下载按钮 */
public void downLoad(DownLoadInfo info) {
// 先把状态添加到这个集合中,如果以后改变info的值,集合中的值也会同步,并不是变化,因为引用的是同一个对象
mDownLoadInfoMaps.put(info.packageName, info);
/* ############### 当前状态: 未下载 ############### */
info.state = STATE_UNDOWNLOAD;
notifyObservers(info);
/* ####################################### */
// 先等待看看能否进入下载,然后再做判断
/* ############### 当前状态: 等待状态 ############### */
info.state = STATE_WAITINGDOWNLOAD;
notifyObservers(info);
// 得到线程池,执行任务
// 真正的下载当然是开启线程咯
DownLoadTask downLoadTask = new DownLoadTask(info);
info.task=downLoadTask;//储存当前线程
ThreadPoolFactory.getdownloadProxy().execute(downLoadTask);
}
class DownLoadTask implements Runnable {
private DownLoadInfo mInfo;
private InputStream in;
private FileOutputStream out;
private DownLoadTask(DownLoadInfo minfo) {
super();
this.mInfo = minfo;
}
// 由于线程里也需要改变下载状态,所以需要把download信息传进去
@Override
public void run() {
LogUtils.sf("我为何可以进来呢?");
try {
// 进来后就要开始下载咯
/* ############### 当前状态: 下载中 ############### */
mInfo.state = STATE_DOWNLOADING;
notifyObservers(mInfo);
// 如果要实现断点下载,就需要记录下载的进度,实际的进度直接读取文件夹下载大小即可
long initRange = 0;
File saveApk = new File(mInfo.savePath);
if (saveApk.exists()) {
initRange = saveApk.length();
}
// 更新当前进度
mInfo.curProgress = initRange;
// 之后就要真正开始下载了
String url = URLS.DOWNLOADBASEURL;
HttpUtils httpUtils = new HttpUtils();
// 请求参数
RequestParams params = new RequestParams();
//第一个参数是下载网址,第二个是下载断点
params.addQueryStringParameter("name",mInfo.downloadUrl);
params.addQueryStringParameter("range",initRange+"");
ResponseStream sendSync = httpUtils.sendSync(HttpMethod.GET, url, params);
if(sendSync.getStatusCode()==200){
in = sendSync.getBaseStream();
File saveFile = new File(mInfo.savePath);
out = new FileOutputStream(saveFile,true);
//用来判断是否暂停
boolean isPause=false;
byte[] buffer=new byte[1024];
int len=0;
while((len=in.read(buffer))!=-1){
//如果是暂停状态就要立马跳出
if(mInfo.state==STATE_PAUSEDOWNLOAD){
isPause=true;
break;
}
//0代表偏移量,因为每次都是追加的写,所以都都是以0开始
out.write(buffer,0,len);
//更新当前进程
mInfo.curProgress+=len;
//并且还是下载状态
/*############### 当前状态: 下载中 ###############*/
mInfo.state = STATE_DOWNLOADING;
notifyObservers(mInfo);
}
//跳出之后判断是否暂停,从而改变不同的状态
if (isPause) {// 用户暂停了下载走到这里来了
/*############### 当前状态: 暂停 ###############*/
mInfo.state = STATE_PAUSEDOWNLOAD;
notifyObservers(mInfo);
/*#######################################*/
} else {// 下载完成走到这里来
/*############### 当前状态: 下载完成 ###############*/
mInfo.state = STATE_DOWNLOADED;
notifyObservers(mInfo);
/*#######################################*/
}
}else{
/*############### 当前状态: 下载失败 ###############*/
mInfo.state = STATE_DOWNLOADFAILED;
notifyObservers(mInfo);
}
} catch(Exception e) {
/*############### 当前状态: 下载失败 ###############*/
mInfo.state = STATE_DOWNLOADFAILED;
notifyObservers(mInfo);
e.printStackTrace();
}finally{
IOUtils.close(in);
IOUtils.close(out);
}
}
}
//detailholder那边总需要知道下载的状态呀,于是暴露一个外部的方法
/**
* @des 暴露当前状态,也就是需要提供downLoadInfo
* @call 外界需要知道最新的state的时候
*给一个download就可以知道状态了
*/
public DownLoadInfo getDownLoadInfo(AppInfoBean data){
//如果应用安装了,可以直接从文件中测出来了
if(CommonUtils.isInstalled(UIUtils.getContext(), data.packageName)){
DownLoadInfo generateDownLoadInfo = generateDownLoadInfo(data);
generateDownLoadInfo.state=STATE_INSTALLED;
return generateDownLoadInfo;
}
//如果文件下载完成了,也可以通过访问是否存在文件测出来
DownLoadInfo generateDownLoadInfo = generateDownLoadInfo(data);
File isfile = new File(generateDownLoadInfo.savePath);
if(isfile.exists()){
if(isfile.length()==data.size){
generateDownLoadInfo.state=STATE_DOWNLOADED;
return generateDownLoadInfo;
}
}
//下面几个状态就是不是那么好确定了
/**
下载中
暂停下载
等待下载
下载失败
*/
//这些信息咱们刚才的hashMap就派上用场了,哈哈
DownLoadInfo hashInfo = mDownLoadInfoMaps.get(data.packageName);
if(hashInfo!=null){
return hashInfo;
}
//如果上边的情况都没有发生,就是是未下载咯
generateDownLoadInfo.state=STATE_UNDOWNLOAD;
return generateDownLoadInfo;
}
/**
* 根据AppInfoBean生成一个DownLoadInfo,进行一些常规的赋值,也就是对一些常规属性赋值(除了state之外的属性)
* 把appbean转换成为downloadbean,就这一个意思,其中状态不需要去管
*/
public DownLoadInfo generateDownLoadInfo(AppInfoBean data) {
DownLoadInfo downLoadInfo = new DownLoadInfo();
downLoadInfo.max=data.size;
downLoadInfo.downloadUrl=data.downloadUrl;
downLoadInfo.packageName=data.packageName;
downLoadInfo.curProgress=0;
//储存路径比较麻烦,需要专门获取一下
String dir = FileUtils.getDir("download");
File file=new File(dir,data.packageName+".apk");
String absolutePath = file.getAbsolutePath();
downLoadInfo.savePath=absolutePath;
return downLoadInfo;
}
/**取消下载 似乎好解决点,移除任务栈就可以了*/
public void cancel(DownLoadInfo info) {
Runnable task = info.task;
ThreadPoolFactory.getdownloadProxy().remove(task);
/*############### 当前状态: 未下载 ###############*/
info.state = STATE_UNDOWNLOAD;
notifyObservers(info);
}
/**暂停下载*/
public void pause(DownLoadInfo info) {
//改变一下状态就可以了,凭什么?
//因为我们在while下载循环里面已经做好了判断,哈哈
/*############### 当前状态: 暂停 ###############*/
Runnable task = info.task;
ThreadPoolFactory.getdownloadProxy().remove(task);
info.state = STATE_PAUSEDOWNLOAD;
notifyObservers(info);
}
//为了把信息传送给需要数据的类吧,比如自定义按钮肯定需要
/*=============== 自定义观察者设计模式 begin ===============*/
//1.定义接口方法
public interface DownLoadObserver{
void onDownLoadInfoChange(DownLoadInfo info);
}
//2.声明变量
List<DownLoadObserver> downLoadObservers = new LinkedList<DownLoadObserver>();
//3.增删方法
/**添加观察者*/
public void addObserver(DownLoadObserver observer) {
if (observer == null) {
throw new NullPointerException("observer == null");
}
synchronized (this) {
if (!downLoadObservers.contains(observer))
downLoadObservers.add(observer);
}
}
/**删除观察者*/
public synchronized void deleteObserver(DownLoadObserver observer) {
downLoadObservers.remove(observer);
}
//4.通知观察者调用的方法,既改变数据,显然每当状态改变的时候都
public void notifyObservers(DownLoadInfo info) {
for (DownLoadObserver p : downLoadObservers) {
p.onDownLoadInfoChange(info);
}
}
/*=============== 自定义观察者设计模式 end ===============*/
}
总之,在Android中观察者模式还是被用得很频繁的。
相应的实现类只要实现DownLoadObserver接口,实现更新方法即可做出相应的改变啦
实现的观察者
public class HomeHolder extends BaseHolder<AppInfoBean> implements
OnClickListener, DownLoadObserver {
//收到数据,改变更新
@Override
public void onDownLoadInfoChange(final DownLoadInfo info) {
// TODO Auto-generated method stub
//先过滤
if(info.packageName!=mData.packageName){
return;
}
UIUtils.postTaskSafely(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
refreshCircleProgressViewUI(info);
}
});
}