java 设计模式之 -- 观察者模式

定义

观察者模式(有时又被称为发布( 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);
}
});
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值