1 解决的问题
响应式编程问题。期望做到,一个对象做出某种反应后,监听者们能够获得该对象的反应。例如,警察关注着张三的一举一动,张三有违法行为,警察迅速行动,抓捕张三。
2 观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
3 实现代码
// 观察者:昆虫接口
public interface Insect {
void startWork();
void stopWork();
}
// 具体观察者:蜜蜂
public class Bee implements Insect {
private String name;
public Bee(String name) { this.name = name; }
@Override public void startWork() { System.out.println("蜜蜂【" + name + "】开始传粉"); }
@Override public void stopWork() { System.out.println("蜜蜂【" + name + "】停止传粉"); }
}
// 具体观察者:蝴蝶
public class Butterfly implements Insect {
private String name;
public Butterfly(String name) { this.name = name; }
@Override public void startWork() { System.out.println("蝴蝶【" + name + "】开始传粉"); }
@Override public void stopWork() { System.out.println("蝴蝶【" + name + "】停止传粉"); }
}
// 被观察者:植物接口
public interface Plant {
void registerInsect(Insect insect);
void unregisterInsect(Insect insect);
void notifyInsect(boolean isOpen);
}
// 具体被观察者:花类
public class Flower implements Plant {
private final List<Insect> insects = new ArrayList<>();
@Override public void registerInsect(Insect insect) { insects.add(insect); }
@Override public void unregisterInsect(Insect insect) { insects.remove(insect); }
@Override public void notifyInsect(boolean isOpen) {
if(isOpen) {
for(Insect insect: insects) insect.startWork();
} else {
for(Insect insect: insects) insect.stopWork();
}
}
// 定义了一个批量解绑的方法
public void unregisterAllInsect() {
for(int i = 0; i < insects.size(); i++) unregisterInsect(insects.get(i));
}
}
// 测试用例
public class ObserverTest {
public static void main(String[] args) {
Flower flower = new Flower();
// 创建并注册观察者
for (int i = 1; i < 4; i++) {
flower.registerInsect(new Bee(i + ""));
flower.registerInsect(new Butterfly(i + ""));
}
// 通知观察者
flower.notifyInsect(true);
System.out.println("=== 开花期已过 ===");
// 通知观察者
flower.notifyInsect(false);
// 解绑所有观察者
flower.unregisterAllInsect();
}
}
4 观察者模式的推与拉
推方式
被观察者 → 观察者推送主题的
详细信息(通常是被观察者的全部或部分数据),不管观察者是否需要。
拉方式
被观察者 → 观察者,只传递
少量信息,如果观察者需要更详细的信息,可主动到被观察者中获取,一般的实现方式是被观察者自身通过 update()方法传递给观察者,观察者再通过这个实例按需获取。
5 优缺点
优点
- 开闭原则,无需修改发布者代码就可以引入新的订阅者类;
- 运行时动态的修改对象之间的关系;
- 将观察者与被观察者之间的抽象解耦。
缺点:
- 通知的顺序是随机的。
- 观察者与被观察者互相依赖时,出现死循环。
- 观察者对象多时,被观察者通知观察者的时间变长,影响程序效率。
6 应用场景
- 对象状态改变需要修改其他对象时;
- 存在订阅者,想订阅发布者;
- 存在发布者,但不需要知道谁接受消息;
- 链式触发机制:在系统中构建一个触发链,A 影响 B、B 影响 C;
7 经典应用例子
7.1 java.util.Observer & java.util.Observable
Java 的java.util包中,提供了一个 Observable 类和 Observer 接口,让我们可以更便捷地实现观察者模式。
java.util.Observer 接口
public interface Observer {
void update(Observable o, Object arg);
}java.util.Observable 类
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable (){};
protected synchronized void setChanged ( ){};
protected synchronized void clearChanged ( ){};
public synchronized void addObserver ( Observer o){};
public synchronized void deleteObserver ( Observer o){};
public synchronized void deleteObservers (){};
public synchronized boolean hasChanged ();
public synchronized int countObservers (){};
public void notifyObservers(){};
public void notifyObservers(Object arg){};
}核心用法:被观察者实现继承 Observable,观察者实现 Observer 接口,通知变化时,调用 setChange 方法
简单的代码示例如下:
// 被观察者
import java.util.Observable;
import java.util.Observer;
public class CodingBoy extends Observable {
private String title;
private String contentUrl;
public String getTitle() { return title; }
public String getContentUrl() { return contentUrl; }
public void update(String title, String url) {
this.title = title;
this.contentUrl = url;
System.out.println("抠腚男孩公众号更新了文章:" + title);
this.setChanged(); // 必不可少,通知改变
this.notifyObservers(this); // 这里用拉方式
}
}
// 观察者
public class Fan implements Observer {
private String name;
public Fan(String name) { this.name = name; }
@Override
public void update(Observable o, Object arg) {
// 拉方式,通过实例按需获取所需信息
CodingBoy codingBoy = (CodingBoy) arg;
System.out.println(codingBoy.getTitle() + codingBoy.getContentUrl());
}
}
// 测试用例
public class ClientTest {
public static void main(String[] args) {
CodingBoy codingBoy = new CodingBoy();
// 注册观察者
for (int i = 1; i < 4; i++) codingBoy.addObserver(new Fan(i + ""));
codingBoy.update("this is title-arg0", "this is url-arg1");
// 取消注册观察者
codingBoy.deleteObservers();
}
}