很快又到年底了,一年一度的春节除了可以享受愉快地享受假期之外,每年的"春节联欢晚会"也是让人倍感期待。然而,大多数人都没能坐在电视机跟前等着它的开始,有的人在厨房忙着准备丰富的食物,有的在一起打麻将、玩牌,还有的可能在玩电脑游戏……大家都想看晚会,于是派出一个小朋友,告诉他:"如果晚会开始了,你要立刻来通知我们哦!"虽然,大家就可以继续做自己的事情,小朋友就坐在电视跟前,当晚会开始的时候,他就立即跑去挨个通知大家……

observer view
观察者模式示意(图片来源网络)

模式源于生活,上边的这个场景,就是一个典型的观察者模式的例子。

1. 概念

先来看看观察者模式的定义。

DP 对观察者模式的定义

观察者模式(Observer),又叫发布-订阅(publish-subscribe)模式,定义了对象间的一种一对多的依赖关系,当一个对象发生改变时,所有依赖它的对象都得到通知并被自动更新。

生活中,有很多这样的一个对象依赖其他多个对象(一对多关系)的例子,该对象状态发生变化了,其他关联的对象都需要知道并且做出行动。如上边的小孩儿看到晚会开始了,要立即通知其他人,其他人可以选择去看春晚,也可以选择继续做自己的事情。又比如,路口红绿灯变绿时,意思是告诉大家可以通行了;微信群有人发消息了,群成员都可以看到这条消息,等等……

在软件领域,观察者模式的运用非常多,如:消息队列(MQ) 可以视为观察者模式实现;又比如,响应式编程 的核心就是观察者模式。

2. 结构

观察者模式类图如下:

observer class

可以看到,观察者模式有4个角色:

  1. Subject: 抽象主题对象,又叫做被观察者(Observable),或者目标对象,定义了添加、删除和通知观察者的方法,知道注册的观察者列表

  2. ConcreteSubject: 具体主题对象,实现 Subject,有自身的状态,状态变化后向各个 Observer 发出通知

  3. Observer: 抽象观察者,定义观察者的通用接口,包含一个用于更新的 update 方法

  4. ConcreteObserver: 具体观察者,实现 Observer,依赖 ConcreteSubject ,当 ConcreteSubject 变化时得到通知,自身也可以更改 ConcreteSubject 的状态,此时可以通知其他观察者更新

上边的几个角色,将抽象和实现相分离,主题对象和观察者都可以各自进行扩展。另外,并不总是主题对象调用 notify 方法去通知观察者们,观察者也可以调用它从而通知其他观察者。

优点:

  1. 观察者模式降低了对象间的耦合性,提高了复用性,符合依赖倒置原则

  2. 主题对象广播通知给所有观察者,并不关心具体的观察者是谁,易于添加和删除观察者,而具体的更新与否以及如何更新由观察者自身实现

缺点:

  1. 观察者自身并不知道其他观察者的存在,它对更改主题状态的代价一无所知,因此需要定义和维护依赖准则,否则可能引起错误更新

  2. 主题和观察者之间仍然存在耦合性(没有完全解耦),存在相互依赖关系甚至可能造成循环依赖

3. 适用场景

观察者模式的使用场景:

  1. 对象间存在一对多的依赖关系,双方都需要独立的扩展和复用

  2. 一个对象的改变会同时改变其他依赖对象

  3. 一个对象必须通知其他对象,但是并不知道其他对象具体是谁

4. 示例

接下来看看观察者模式的示例代码(注意,下边的代码没有考虑并发安全性)。

1、定义观察者

public interface Observer {
    void update(); (1)
}
1观察者自身更新方法

2、定义主题对象

先定义一个可观察接口:

public interface Observable {
    void attach(Observer observer); (1)
    void detach(Observer observer); (2)
    void notifyObservers(); (3)
    long count(); (4)
}
1添加一个观察者
2移除一个观察者
3通知所有观察者
4返回观察者数量

然后,定义抽象的主体对象:

public abstract class Subject implements Observable {
    private static final List<Observer> OBSERVERS = new ArrayList<>();
    @Override
    public void attach(Observer observer) {
        OBSERVERS.add(observer);
    }
    @Override
    public void detach(Observer observer) {
        OBSERVERS.remove(observer);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : OBSERVERS) {
            observer.update();
        }
    }
    public abstract String name(); (1)
    @Override
    public long count() {
        return OBSERVERS.size();
    }
}
1示例抽象方法,返回主体对象名称

3、定义具体主体对象

public class ConcreteSubject extends Subject {
    private String state; (1)
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    @Override
    public String name() {
        return "具体观察者";
    }
}
1具体主题对象有一个状态,可以被读取和更改

4、定义具体观察者

public class ConcreteObserver implements Observer {
    private static int count = 0;
    private final int id = count++; (1)
    private ConcreteSubject subject;
    public ConcreteObserver(ConcreteSubject subject) {
        this.subject = subject;
    }
    @Override
    public void update() {
        System.out.println("观察者 " + id + ": 主体 [" + this.subject.name() + "] 状态变成了:" + this.subject.getState());
    }
    public ConcreteSubject getSubject() {
        return subject;
    }
    public void setSubject(ConcreteSubject subject) {
        this.subject = subject;
    }
}
1这里给每一个具体观察者一个id,以便区分

5、客户端调用

public class ObserverClient {
    public static void main(String[] args) {
        // 一个主体
        ConcreteSubject subject = new ConcreteSubject();
        subject.setState("原始状态");
        // 两个观察者
        ConcreteObserver observer1 = new ConcreteObserver(subject);
        ConcreteObserver observer2 = new ConcreteObserver(subject);
        subject.attach(observer1);
        subject.attach(observer2);
        // 状态变化了,通知更新
        subject.setState("状态更改");
        subject.notifyObservers();
    }
}

输出结果如下:

观察者 0: 主体 [具体观察者] 状态变成了:状态更改
观察者 1: 主体 [具体观察者] 状态变成了:状态更改

5. Java语言级的观察者

上一节的示例代码中,我们定义了 ObservableObserver 分别代表可观察对象(主题)和观察者对象。其实,早在 Jdk1.0 Java就已经提供了这两个对象,他们位于 java.util 包中。

1、JDK1.0 的 Observer 的定义:

public interface Observer {
    void update(Observable o, Object arg); (1)
}
1更新方法,传递可观察对象参数和一个传递到 notifyObservers 方法的参数对象

2、JDK1.0 的 Observable:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable() {
        obs = new Vector<>();
    }
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
        notifyObservers(null);
    }
    public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }
    protected synchronized void setChanged() {
        changed = true;
    }
    protected synchronized void clearChanged() {
        changed = false;
    }
    public synchronized boolean hasChanged() {
        return changed;
    }
    public synchronized int countObservers() {
        return obs.size();
    }
}

可以看到,该类提供了线程安全的 api,内部使用 Vector 作为存储 Observer 的容器,同时还存储一个 changed 状态,只有该状态为 true 时才会进行通知。

虽然前边示例代码定义的这两个类也实现了观察者模式的基本功能,但是没有考虑线程安全性,JDK 提供的这两个类更完整,可以直接使用,也可以根据需求自定义实现。

6. 总结

观察者模式定义了对象之间的一对多的依赖关系,抽象了主题对象和观察者,使得系统可以很容易扩展它们。但是,由于主题对象会通知所有注册的观察者,而且观察者对其他观察者并不感知,所以当某一个观察者触发主题状态修改并通知时,如果没有定义和遵照依赖准则,可能付出较大的代价。

本文示例代码见: github


相关阅读