1. 概念
定义对象之间一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
2. 使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面;
- 一个对象的改变将导致一个或多个其他对象也发送改变;
- 需要在系统中创建一个触发链。
3. UML
抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
- Subject 和 Observer 是一个一对多的关系,也就是说观察者只要实现 Observer 接口并把自己注册到 Subject 中就能够接收到消息事件;
- Java API有内置的观察者模式类:java.util.Observable 类和 java.util.Observer 接口,这分别对应着 Subject 和 Observer 的角色;
- 使用 Java API 的观察者模式类,需要注意的是被观察者在调用 notifyObservers() 函数通知观察者之前一定要调用 setChanged() 函数,要不然观察者无法接到通知; 其中使用了Vector,保证了线程安全;当然,使用CopyOnWriteArrayList是可以替代vector的,保证了线程安全;
- 使用 Java API 的缺点也很明显,由于 Observable 是一个类,java 只允许单继承的缺点就导致你如果同时想要获取另一个父类的属性时,你只能选择适配器模式或者是内部类的方式,而且由于 setChanged() 函数为 protected 属性,所以你除非继承 Observable 类,否则你根本无法使用该类的属性,这也违背了设计模式的原则:多用组合,少用继承。
4. 示例
MyPerson是被观察者
public class MyPerson extends Observable {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
setChanged();
notifyObservers();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
setChanged();
notifyObservers();
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
setChanged();
notifyObservers();
}
@Override
public String toString() {
return "MyPerson [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
setChanged()
告知数据改变,通过notifyObservers()
发送信号通知观察者。
MyObserver是观察者
public class MyObserver implements Observer {
private int id;
private MyPerson myPerson;
public MyObserver(int id) {
System.out.println("我是观察者---->" + id);
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public MyPerson getMyPerson() {
return myPerson;
}
public void setMyPerson(MyPerson myPerson) {
this.myPerson = myPerson;
}
@Override
public void update(Observable observable, Object data) {
System.out.println("观察者---->" + id + "得到更新");
this.myPerson = (MyPerson) observable;
System.out.println(((MyPerson) observable).toString());
}
}
观察者接受到通知后,调用update()
方法进行更新操作。
5. Android中的使用
1. 回调模式
实现了抽象类/接口的实例同时实现了父类提供的抽象方法后,将该方法交还给父类来处理
在Android中button的点击事件setOnClickListener
就是一种,button
是被观察者,OnClickListener
是观察者。
2. notifyDataChanged()
当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者(AdapterDataSetObserver)的onChanged方法,在onChanged函数中又会调用ListView重新布局的函数使得ListView刷新界面。
6. 特点
优点:
观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
缺点:
依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者。