一、观察者模式的定义
定义对象间一对多的依赖关系,使得当前对象改变了状态,则所有依赖于它的对象都会得到通知并自动更新。
二、观察者模式的使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面;(比如:View依赖于Model的数据,当数据发生改变时,更新View)
- 一个对象的改变将导致一个或多个其他对象也发生改变;(比如:同一个数据源对多个观察对象)
- 需要在系统创建一个触发链。(比如A影响B,B影响C)
三、UML结构图
- Subject:抽象主题,也就是被观察者(Observable),把所有的观察者对象的引用保存在一个集合里。每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,会给所有注册过的观察者发出通知notify。
- Observer:抽象观察者,它定义了更新接口,使得在得到被观察者都更改通知时更新自己。
- ConcreteObserver:具体观察者,实现了抽象观察者所定义的更新接口。
四、实现1(自己写Observer和Observable)
/*
* 抽象被观察者,添加删除通知观察者。
*/
public abstract class Subject {
//保存注册观察者对象
private Vector<Observer> mObservers = new Vector<>();
//注册观察者对象
public void addObserver(Observer o) {
this.mObservers.add(o);
}
//注销观察者对象
public void removeObserver(Observer o) {
this.mObservers.remove(o);
}
//通知观察者对象
public void notifyObservers() {
for (Observer o : this.mObservers) {
o.update();
}
}
}
/*
* 具体被观察者
*/
public class ConcreteSubject extends Subject {
public void doSomething() {
System.out.println("doSomething");
this.notifyObservers();
}
}
/*
* 抽象观察者
*/
public interface Observer {
void update();
}
/*
* 具体观察者
*/
public class ConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("收到消息");
}
}
/*
* 测试类
*/
public class Client {
public static void main(String[] args) {
ConcreteSubject cs = new ConcreteSubject();
Observer observer1 = new ConcreteObserver();
Observer observer2 = new ConcreteObserver();
cs.addObserver(observer1);
cs.addObserver(observer2);
cs.doSomething();
}
}
- 显示结果:
doSomething
收到消息
收到消息
五、实现2(实际开发中一般是使用java.util包下面的Observer、Observable)
/*
* 观察者
*/
class EmailUser implements Observer {
public String name;
public EmailUser(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("亲" + name + "," +
"你关注的栏目《" + arg + "》已经更新,请及时查看!");
}
}
/*
*被观察者
*/
class Emailcolumn extends Observable {
public void postNewColumn(String content) {
setChanged();
notifyObservers(content);
}
}
/*
* 测试代码
*/
public class ObserverDemo {
public static void main(String[] args) {
Emailcolumn emailcolumn = new Emailcolumn();
EmailUser user1 = new EmailUser("user1");
EmailUser user2 = new EmailUser("user2");
emailcolumn.addObserver(user1);
emailcolumn.addObserver(user2);
emailcolumn.postNewColumn("今天最开心:雷佳音");
}
}
- 显示结果:
亲user2,你关注的栏目《今天最开心:雷佳音》已经更新,请及时查看!
亲user1,你关注的栏目《今天最开心:雷佳音》已经更新,请及时查看!
六、观察者模式在Android中的实际运用
- 回调函数:一对一的关系
- ListView数据更新
- RxJava
- 广播注册
- EventBus
1.回调函数
定义:实现了抽象类/接口的实例,实现了父类提供的抽象方法后,将该方法交还给父类来处理。
public class Employee {
//定义回调接口的成员变量
private Callback mcallback;
//声明回调接口
public interface Callback {
public abstract void work();
}
//设置回调接口对象成员变量
public void setCallback(Callback callback) {
this.mcallback = callback;
}
//调用回调接口对象中的方法
public void doWork() {
if(mcallback!=null){
mcallback.work();
}
}
}
public class Boss {
private Employee employee;
//为Employee设置回调函数,在这里定义具体的回调方法
public void setCallback() {
employee.setCallback(new Employee.Callback() {
@Override
public void work() {
System.out.println("work");
}
});
}
}
再来看另一个回调的例子:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// do something
}
});
OnClickListener作为观察者,button作为被观察者,通过setOnClickListener来形成订阅关系。当button被点击时相当于被观察者数据发生改变,就会去通知观察者OnClickListener,这时触发onClick(回调onClick)。
- 面试题:回调函数和观察者模式的区别?
- 观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。观察者模式完美的将观察者和被观察的对象分离开,一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
- 回调函数:其实也算是一种观察者模式的实现方式,回调函数实现的观察者和被观察者往往是一对一的依赖关系。
- 所以最明显的区别是观察者模式是一种设计思路,而回调函数式一种具体的实现方式;另一明显区别是一对多还是多对多的依赖关系方面。
相关阅读:Android面试一天一题(Day 43:设计模式)
2.ListView的notifyDataSetChanged
当 ListView 的数据发生变化时,调用 Adapter 的 notifyDataSetChanged 函数,这个函数又会调用 DataSetObservable 的 notifyChanged 函数,这个函数会调用所有观察者 (AdapterDataSetObserver) 的 onChanged 方法,在 onChanged 函数中又会调用 ListView 重新布局的函数 requestLayout()使得 ListView 刷新界面。
七、观察者模式的缺点
- 开发效率问题。一个被观察者,多个观察者,开发和调试就会比较复杂。
- 运行效率问题。多个观察者、多级触发等情况下,因为在Java默认是顺序执行,所以一个观察者卡壳,会影响整体的执行效率,这时候推荐考虑采用异步的方式。
八、观察者模式的优点
- 观察者和被观察者之间是抽象耦合,观察者和被观察者的扩展比较方便。
- 建立一套触发机制。例如建立一条触发链。
九、总结
观察者模式主要的作用就是对象解耦,将观察者与被观察者完全隔离,只依赖与 Observer 和 Obserable 抽象。例如LisetView就是运用了Adapter和观察者模式使得它的可扩展性、灵活性非常强,而耦合的很低,这些设计模式在Android源码中优秀运用的典范。
- 那么如何达到低耦合、高灵活性的代码?
- 广播接收器和时间总线在观察者模式上有什么相似和不相同的地方?
-
RxJava
是怎么运用观察者模式的?
十、参考书籍
- 《Android 源码设计模式解析与实战》
- 小楠总的设计模式之旅14