您的位置:首页 > 移动开发 > Android开发

java/android 设计模式学习笔记(2)---观察者模式

2016-05-15 20:43 761 查看
  这篇来讲一下观察者模式,观察者模式在实际项目中使用的也是非常频繁的,它最常用的地方是GUI系统、订阅——发布系统等。因为这个模式的一个重要作用就是解耦,使得它们之间的依赖性更小,甚至做到毫无依赖。以GUI系统来说,应用的UI具有易变性,尤其是前期随着业务的改变或者产品的需求修改,应用界面也经常性变化,但是业务逻辑基本变化不大,此时,GUI系统需要一套机制来应对这种情况,使得UI层与具体的业务逻辑解耦,观察者模式此时就派上用场了。

  转载请注明出处:http://blog.csdn.net/self_study/article/details/51346849

  PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

  java/android 设计模式学习笔记目录

特点

  观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。它实现了 Subject 和 Observer 之间的松耦合,Subject只知道观察者实现了 Observer 接口,主题不需要知道具体的类是谁、做了些什么或其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer 接口的对象列表,所以我们可以随时增加观察者,同样的,也可以在任何时候删除观察者,当然更不用去关心观察者的类型,只要实现了Observer接口即可,Subject 最后只会发通知给实现了 Observer 接口的观察者。Subject 和 Observer 之间实现松耦合之后,双方代码的修改都不会影响到另外一方,当然前提是双方得遵守接口的规范(接口隔离原则)。

  观察者模式使用的场景:
关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系;
事件多级触发场景;
跨系统的消息交换场景,如消息队列、事件总线的处理机制。
EventBus 框架就是一个使用观察者模式的典型例子。

UML类图

  


  观察者模式的uml类图如上图所示,一般基本由4个角色构成
Subject:抽象主题,也就是被观察的角色,抽象主题把所有观察者对象的引用保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发出通知,具体主题角色对象又叫做具体被观察者角色。
Observer:抽象观察者,该角色是观察者的抽象类,它定义了一个更新接口,使得在得到主题的更改通知时更新自己。
ConcreteObserver:具体的观察者,该对象实现抽象观察者角色所定义的更新接口,以便在主题的状态变化时更新自身的状态。
  有几点需要注意的是Subject 和 Observer 是一个一对多的关系,也就是说观察者只要实现 Observer 接口并把自己注册到 Subject 中就能够接收到消息事件;
Java API有内置的观察者模式类:java.util.Observable 类和 java.util.Observer 接口,这分别对应着 Subject 和 Observer 的角色;
使用 Java API 的观察者模式类,需要注意的是被观察者在调用 notifyObservers() 函数通知观察者之前一定要调用 setChanged() 函数,要不然观察者无法接到通知;
使用 Java API 的缺点也很明显,由于 Observable 是一个类,java 只允许单继承的缺点就导致你如果同时想要获取另一个父类的属性时,你只能选择适配器模式或者是内部类的方式,而且由于 setChanged() 函数为 protected 属性,所以你除非继承 Observable 类,否则你根本无法使用该类的属性,这也违背了设计模式的原则:多用组合,少用继承。

示例与源码

  观察者模式写法有很多种,只要遵循将被观察者和观察者解耦思想的方法都是可以的,列举三种我在开发中常见的方法,以 firstmoduel 作为 secondmodule1 和 secondmodule2 的父 module ,最后 application 以 secondmoduel1 和secondmodule2 作为父 module,形成一个菱形的关系为例:

Java API

  使用Java API的写法其实就是使用 java.util.Observable 类和 java.util.Observer 接口,根据 uml 类图构造观察者,我们简单实现一个在 secondmodule2 中通知 secondmodule1有数据源的改变,并且让 secondmodule1 打印出对应日志的功能。因为 secondmodule1 和 secondmodule2 是一个同级关系,所以无法相互调用,只能通过 firstmodule 作为主题使用观察者模式进行通信。

  Subject 在使用 Java API 时其实就是 Observable 类,所以我们要在 firstmodule 中继承该类实现一个 ConcreteSubject 用来保存所有的观察者,接着在 secondmodule1 中实现 Observer 接口来生成一个观察者,最后在secondmodule2 中通知 firstmodule 数据源改变,最后通过 firstmodule 通知到 secondmodule1 ,代码如下:

firstmoduel/DataObservable.class 中利用单例模式进行观察者的管理和通知:

public class DataObservable extends Observable{

private static volatile DataObservable instance;
private DataObservable() {}

public static DataObservable getInstance() {
if (instance == null){
synchronized (DataObservable.class) {
if (instance == null){
instance = new DataObservable();
}
}
}
return instance;
}

public void notifyDataChanged(DataBean data) {
setChanged();
notifyObservers(data);
}
}


firstmoduel/DataBean.class 数据类:

public class DataBean {
public int temperature;
}


secondmodule1/DataObserver.class 中判断如果为正确的 Observerable 则打印日志:

public class DataObserver implements Observer {
private static final String TAG = "DataObserver";

@Override
public void update(Observable observable, Object data) {
if (observable instanceof DataObservable){
if (data instanceof DataBean){
Log.e(TAG, ((DataBean)data).temperature+"");
}
}
}
}


secondmodule2/DataNotify.class

public class DataNotify {
public static void notifyDataChanged(){
DataBean bean = new DataBean();
bean.temperature = (int) (Math.random() * 40);
DataObservable.getInstance().notifyDataChanged(bean);
}
}


最后在 MainActivity 中调用 DataNotify 类的相应方法通知 DataObserver 温度数据变更即可:

@Override
protected void onCreate(Bundle savedInstanceState) {
...
DataObservable.getInstance().addObserver(new DataObserver());
}

@Override
protected void onDestroy() {
super.onDestroy();
DataObservable.getInstance().deleteObserver(new DataObserver());
}
...
public void onClick(View v) {
if (v.getId() == R.id.btn_data_change_1){
DataNotify.notifyDataChanged();
}
...
}


最后能够成功打印出正确日志,也就是实现了 android 中两个同级 module 之间的通信。

  当然如果觉得麻烦不使用 Java API 的这两个类,自己去实现的话也是完全可以的,而且有时可操纵性会比 Java API 更好一些。

multi Observer

  有时候我们不一定全部要实现自同一个 Observer 接口,根据实际情况我们可以在完全解耦的情况下将多个 Observer 注册到 Subject 中,并且根据情况只通知到我们想要通知的一个 Observer 中,听起来很乱,其实看一张 uml 图就清楚了:

  

图片" title="">

  这种方式其实最重要的就是 Subject 这个角色了,他承担了大部分的工作量,我们先实现若干个 Observer 的角色:

firstmodule/IDataListenerOne.class

public interface IDataListenerOne {
void OnDataChanged(DataBean data);
}


firstmodule/IDataListenerTwo.class

public interface IDataListenerTwo {
void OnDataChanged(DataBean data);
}


非常简单的几个接口,用来在 secondmodule1 和 secondmodule2 之间进行通信。接着是最重要的 Subject 和 ConcreteSubject 角色:

firstmodule/IMultiDataObservable.class

public interface IMultiDataObservable {
/**
* 增加观察者
*/
void addObserver(Object observer);

/**
* 删除观察者
*/
void deleteObserver(Object observer) throws IllegalArgumentException;

/**
* 查找观察者
*/
<T>ArrayList<T> findObserver(Class<T> clazz);
}


firstmodule/MultiDataObservable.class

public class MultiDataObservable implements IMultiDataObservable {

private static volatile MultiDataObservable instance;
private ArrayList<Object> observers;

private MultiDataObservable() {
observers = new ArrayList<>();
}

public static MultiDataObservable getInstance() {
if (instance == null) {
synchronized (MultiDataObservable.class) {
if (instance == null) {
instance = new MultiDataObservable();
}
}
}
return instance;
}

@Override
public void addObserver(Object observer) {
observers.add(observer);
}

@Override
public void deleteObserver(Object observer) throws IllegalArgumentException {
if (observer == null) {
throw new IllegalArgumentException("observer must not be null");
}
if (!observers.remove(observer)) {
throw new IllegalArgumentException("observer not registered");
}
}

@Override
public <T> ArrayList<T> findObserver(Class<T> clazz) {
ArrayList<T> lists = new ArrayList<>();
for (Object observer : observers) {
if (clazz.isInstance(observer)) {
lists.add(clazz.cast(observer));
}
}
return lists;
}
}


这个类主要是有三个方法,增删找,特别是 findObserver 方法,它通过传入的 Class 对象从观察者 list 中找到符合要求的 Observer 并且返回,需要特别留意的一点是需要使用的方法是 Class.isInstance(Object object) 方法,而不是直接判断 object.getclass() == clazz ,因为后者的这种判断如果使用的是匿名内部类方式,他的类名会是 DataCommunicate$1 这种样式,所以这种方法是不可行的。最后在 secondmodule1 和 secondmodule2 中只要根据使用调用相应的方法即可,以secondmodule2 为例:

secondmodule2/DataCommunicate.class

public class DataCommunicate {
private static final String TAG = "DataCommunicate";

private static IDataListenerTwo listenerTwo = null;

public static void registerDataListenerTwo() {
IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
listenerTwo = new IDataListenerTwo() {
@Override
public void OnDataChanged(DataBean data) {
Log.e(TAG, data.temperature + "");
}
};
dataObservable.addObserver(listenerTwo);
}

public static void notifyDataListenerOne() {
IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
ArrayList<IDataListenerOne> lists = dataObservable.findObserver(IDataListenerOne.class);
DataBean bean = new DataBean();
bean.temperature = (int) (Math.random() * 40);
for (IDataListenerOne listener : lists) {
listener.OnDataChanged(bean);
}
}

public static void unRegisterDataListenerTwo() {
IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
dataObservable.deleteObserver(listenerTwo);
}
}


最后也可以成功通信,这种方式的优点是完全可以自定义 Observer 这个接口,接口中的方法可以任意定义,具有很大的自由性,便于统一管理所有的观察者,非常方便。

EventBus

  事件总线大家想必已经看见过很多了,它也是一个典型的观察者模式用例,列举一下我常见的3个框架:
greenrobot/EventBus
AndroidEnventBus
Otto
它们原理也不复杂, 对比之类的网上资料很多,比如Otto 框架的效率大多数时候会比不上 EventBus 等,感兴趣想要具体了解的自己动动手收集一下,我这里以使用最广泛的 greenrobot/EventBus 为例,来实现 secondmodule1 和 secondmodule2 之间的通信:

secondmodule1/EventNotifier.class

public class EventNotifier {
private static volatile EventNotifier instance;

private EventNotifier() {
}

public static EventNotifier getInstance() {
if (instance == null) {
synchronized (EventNotifier.class) {
if (instance == null) {
instance = new EventNotifier();
}
}
}
return instance;
}

public void sendEvent() {
DataBean bean = new DataBean();
bean.temperature = (int) (Math.random() * 40);
EventBus.getDefault().post(bean);
}
}


secondmodule2/EventObserver.class

public class EventObserver {
private static final String TAG = "EventObserver";
private static volatile EventObserver instance;

private EventObserver() {

}

public static EventObserver getInstance() {
if (instance == null) {
synchronized (EventObserver.class) {
if (instance == null) {
instance = new EventObserver();
}
}
}
return instance;
}

public void registerObserver() {
EventBus.getDefault().register(this);
}

public void unRegisterObserver() {
EventBus.getDefault().unregister(this);
}

@Subscribe
public void onEventMainThread(DataBean bean) {
Log.e(TAG, bean.temperature + "");
}
}


特别需要注意的是,onEventMainThread 函数需要加上 @Subscribe 注解,要不然是无法工作。最后在 MainActivity 中调用相关函数即可:

@Override
protected void onCreate(Bundle savedInstanceState) {
...
EventObserver.getInstance().registerObserver();
}

@Override
protected void onDestroy() {
...
EventObserver.getInstance().unRegisterObserver();
}

@Override
public void onClick(View v) {
...
else if (v.getId() == R.id.btn_data_change_3) {
EventNotifier.getInstance().sendEvent();
}
}


最后当然也是能够成功!

总结

  观察者模式在实际开发过程中使用的场景真的挺多,比如 Activity 中两个 fragment 之间的通信,上面描述的两个同级 module 之间的通信,上层 module 与下层 module 的通信,两个 Activity 之间维护一个数据源需要做到数据实时同步等等场景,使用观察者模式之后思路简单清晰,可维护性好,而且最重要当然是耦合性低利于扩展,Android 官方源码中的 Listview 和 Adapter 也是使用了观察者模式的典型例子。

  观察者模式的优点和缺点总结一下,优点:
观察者和被观察者之间是抽象耦合,应对业务变化能力强,可扩展性强;
观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。
同时观察者模式也有一些缺点:观察者模式有时会有开发效率和运行效率的问题,程序中包括一个被观察者和多个观察者时,调试和开发会相对变得复杂;
观察者如果数量很多,所有的观察者都通知到会花费一定时间;
观察者模式中被观察者通知观察者时默认是顺序执行,所以当一个观察者执行缓慢或者卡顿就会影响整体的执行效率,如果有这种情况最好采用异步执行的方式去处理;
如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式时要特别注意这一点。

源码下载

https://github.com/zhaozepeng/Design-Patterns/tree/master/ObserverPattern

引用

http://www.cnblogs.com/zemliu/p/3313782.html

http://blog.csdn.net/luoxianfeng89/article/details/50395901

http://blog.csdn.net/jason0539/article/details/45055233

http://ju.outofmemory.cn/entry/226121

http://www.cnblogs.com/yydcdut/p/4651208.html

http://blog.csdn.net/harvic880925/article/details/40787203

http://bianchengzhe.com/DesignPattern/neirong/221.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  设计模式 java android