3.1 课程概述
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
1. 发布 — 订阅
在首篇导读中介绍了设计模式的分类,而观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。
观察者模式有一个别名叫 “ 订阅 — 发布模式 ” 。报纸大家都订过吧,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,这是典型的订阅 — 发布模式,报社和订报纸的客户就是上面文章开头所说的 “ 一对多 ” 的依赖关系。
观察者模式类图:
其实观察者模式也很简单,从上图可以看出观察者模式就两种角色,一是观察者,二是被观察者( 主题 ),也可以认为是订阅者和发布者。
从逻辑上来说,观察者模式就是:当被观察者的状态改变了,就通知观察者,怎么通知呢?从类图中可以看到,被观察者保存了一份所有观察者的列表,只要调用观察者对象的 update() 方法即可。
用发布 — 订阅报纸的实例来说的话,就是客户们向报社订阅报纸,报社要保存一份所有客户的地址名单,然后有新报纸来了,就按照这个名单一个个派送报纸。
具体的代码实例如下。
观察者(客户):
public abstract class Customer {
public abstract void update();
}
public class CustomerA extends Customer {
@Override
public void update() {
System.out.println("我是客户A,我收到报纸啦");
}
}
public class CustomerB extends Customer {
@Override
public void update() {
System.out.println("我是客户B,我收到报纸啦");
}
}
本例子中 Customer 使用了抽象类,而不是接口,跟上面 UML 中不太一样。再次强调,设计模式传达给我们的是一种编程思想,要变通。
被观察者( 报社 ):
/**
* 报社(被观察者)
*/
public class NewsOffice {
private List<Customer> customers = new ArrayList<>();
public void addCustomer(Customer customer){
this.customers.add(customer);
}
//模拟报纸来了
public void newspaperCome(){
this.notifyAllObservers();
}
public void notifyAllObservers(){
for (Customer customer : customers){
customer.update();
}
}
}
测试:
NewsOffice office= new NewsOffice();
Customer customerA = new CustomerA();
Customer customerB = new CustomerB();
//客户A订阅报纸
office.addCustomer(customerA);
//客户B订阅报纸
office.addCustomer(customerB);
//报纸来了
office.newspaperCome();
运行结果:
观察者模式最重要的一点是要搞清楚到底谁是观察者,谁是被观察者,一定要分清楚,这里笔者送你一句金玉良言,只要记住这一句话,观察者模式就不是问题:收通知的就是观察者。
如果观察者和被观察者这几个字已被混淆,可以用报纸发布订阅来套。客户是订阅报纸,收报纸的人( 收通知 ),所以客户就是观察者,那么报社就是被观察者了。
2. 改进
上面的观察者模式其实还可以做一个小小的改进,很简单,给被观察者上头抽象出一个 ISubject 接口:
这样做有什么好处呢?
我们将被观察者中通用的方法,比如添加观察者、删除观察者、通知观察者等抽象出来,因为这是所有观察者模式中都要用到的三个方法。
我们举的例子是报社订报纸的例子,如果换了其他案例或场景,观察者不再是订报纸的客户,那么需要 New 一个新的观察者类,实现 ISubject 接口。所有的观察者都实现了 ISubject 这个接口,那么是不是就有多态的特性可以利用啦!
比如我们可以不用 Customer ,而用 ISubject ,是不是灵活得多。
public void addObserver(ISubject subject){
//添加被观察者
}
很明显这种方式提供了代码的灵活性,改进后的代码如下。
观察者:
public abstract class Observer {
public abstract void update();
}
public class CustomerA extends Observer {
@Override
public void update() {
System.out.println("我是客户A,我收到报纸啦");
}
}
public class CustomerB extends Observer {
@Override
public void update() {
System.out.println("我是客户B,我收到报纸啦");
}
}
被观察者( 客户 ):
public interface ISubject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
/**
* 报社(被观察者)
*/
public class NewsOffice implements ISubject{
private List<Observer> observers = new ArrayList<>();
//模拟报纸来了
public void newspaperCome(){
this.notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers){
observer.update();
}
}
}
测试:
ISubject office = new NewsOffice();
Observer customerA = new CustomerA();
Observer customerB = new CustomerB();
office.registerObserver(customerA);
office.registerObserver(customerB);
((NewsOffice) office).newspaperCome();
看了改进后的例子,你会发现 NewsOffice 中有重复代码:
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers){
observer.update();
}
}
这三个方法在每一个 ISubject 的实现类中的代码都基本一致,我们并不想重复写,因此可以写一个默认实现类:
public abstract class BaseSubject implements ISubject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers){
observer.update();
}
}
}
/**
* 报社(被观察者)
*/
public class NewsOffice extends BaseSubject{
//模拟报纸来了
public void newspaperCome(){
this.notifyObservers();
}
}
但是其实并不推荐这种方式,这是一种偷懒的做法,虽然方便,但是会带来一些问题:BaseSubject 是一个类,所有的被观察者都要继承这个类,如果被观察者既要拥有 BaseSubject 的能力,又想拥有其他类的能力怎么办?要知道 Java 是不支持多继承的,所以还是主要推荐使用接口的方式。
3. Java 提供的观察者模式
现在我告诉大家一个好消息,Java 已经为我们提供观察者模式所需要的类:Observer 类和 Subject 类,只不过在 Java 中 Subject 不叫 Subject,而叫 Observable。
我们打开 Observable 类看一下它的结构:
有没有发现我们所熟悉的 addObserver() 、notifyObservers() 等方法?
那么 Observable 跟我们自己写的 Subject 有什么区别呢?仔细看一下它的源码你会发现它保存观察者列表不是用 List ,而是用:
private Vector<Observer> obs
同时通知观察者时用了 synchronized 关键字:
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
不得不佩服,Java 考虑到了线程安全问题,跟它比起来我们写的代码被碾成了 “ 渣渣辉 ” 。
所以很多情况下直接使用 Java 提供的就可以了,没必要自己去实现。但这不是绝对的,前面已经说过了,Java 不支持多继承,而 Java 提供的 Observable 是一个类,不是接口,所以会带来一些问题,其中利弊,需要你自己去衡量。
下面给出一个 Demo :
public class NewsOffice2 extends Observable {
/**
* 模拟报纸来了
*/
public void newspaperCome(){
this.setChanged();
this.notifyObservers();
}
}
public class CustomerC implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("我是客户C,我收到消息啦");
}
}
测试:
Observableoffice = new NewsOffice2();
Observer observer = new CustomerC();
office.addObserver(observer);
((NewsOffice2) office).newspaperCome();
仔细对比,你会发现 Java 提供的 Observer 的 update 方法给我们传了 Observable 对象:
@Override
public void update(Observable o, Object arg) {
System.out.println("我是客户C,我收到消息啦");
}
有时候需要传递数据,这个参数就很有用了。
综合上面各个例子,建议:
- 如果只是简单的场景使用观察者模式,可以直接用 Java 提供的。
- 复杂的场景建议自己写,基于接口写,同时可以抄袭一下 Java 内置的 Observer 类中关于线程安全的代码。
3.2 实际案例 ( View-Model )
大名鼎鼎的 MVC 模式大家一定都听过,MVC 分别指 Model 、View 、Controller 。在标准 MVC 模型中,当 Model 改变时,View 视图会自动改变;View 和 Model 之间就是典型的观察者模式。
谁是观察者,谁是被观察者?还记得我在前面说的 “ 金玉良言 ” 吗?收通知的就是观察者。在 View 和 Model 中,显然 View 收通知的那一方,那么 View 就是观察者。
在 Web 应用中,由于 View 是在浏览器端展示,而 Model 是在服务端,因此不好体现观察者模式,但是在桌面应用中体现得淋漓尽致。现在我们来写一个简单的例子。
先来一个 User Model( 被观察者/主题 ):
public class User extends Observable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
this.setChanged();
this.notifyObservers();
}
}
setName 的时候,也就是 Model 改变了,通知观察者们。
来一个 View :
public class MyFrame extends JFrame implements Observer {
private Label nameLabel;
public MyFrame() {
//创建一个label用于显示name
this.nameLabel = new Label();
this.nameLabel.setText("none");
this.getContentPane().add(nameLabel);
//设置窗口大小
this.setLocation(0,0);
this.setSize(100,100);
}
/**
* model改变会调用该方法
* @param o
* @param arg
*/
@Override
public void update(Observable o, Object arg) {
String name = ((User)o).getName();
this.nameLabel.setText(name);
}
}
这个 View 是一个桌面窗口,MyFrame() 构造函数中是一些初始化操作,不懂没关系,可以忽略不看,重点是它是一个观察者。
测试:
Frame frame = new MyFrame();
frame.setVisible(true);
User user = new User();
user.addObserver((Observer) frame);
try {
Thread.sleep(3000);
user.setName("java");
} catch (InterruptedException e) {
e.printStackTrace();
}
为了看到效果,我们会在窗口打开后睡眠 3 秒,然后修改 Model 。
运行,显示窗口如下图所示:
三秒后:
除了 MVC ,大家可能还听过 MVVM ,MVC 是 Model 改变 View 自动更新,而 MVVM 是双向绑定,View 改变,Model 也自动更新。其实原理很简单,双方互为观察者即可。
3.3 课程总结
1.优点
观察者和被观察者之间抽象耦合,自有一套触发机制,被观察者无需知道通知对象是谁,只要是符合观察者接口的就可以。
2.缺点
- 观察者只知道被观察者发生变化,而无法知道是如何发生变化的,比如是修改了 name 字段还是其他,观察者都不知道。
- 如果有很多个观察者,一个个通知比较耗时。