观察者

    观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。

    要理解观察者模式,我们还是看例子。

    假设一个电商网站,有多种(商品),同时,Customer(消费者)和Admin(管理员)对商品上架、价格改变都感兴趣,希望能第一时间获得通知。于是,Store(商场)可以这么写:

    我们观察上述Store类的问题:它直接引用了CustomerAdmin。先不考虑多个Customer或多个Admin的问题,上述Store类最大的问题是,如果要加一个新的观察者类型,例如工商局管理员,Store类就必须继续改动。

    因此,上述问题的本质是Store希望发送通知给那些关心Product的对象,但Store并不想知道这些人是谁。观察者模式就是要分离被观察者和观察者之间的耦合关系。

    要实现这一目标也很简单,Store不能直接引用CustomerAdmin,相反,它引用一个ProductObserver接口,任何人想要观察Store,只要实现该接口,并且把自己注册到Store即可:

    1. public class Store {
    2. private List<ProductObserver> observers = new ArrayList<>();
    3. private Map<String, Product> products = new HashMap<>();
    4. // 注册观察者:
    5. public void addObserver(ProductObserver observer) {
    6. this.observers.add(observer);
    7. }
    8. public void removeObserver(ProductObserver observer) {
    9. this.observers.remove(observer);
    10. }
    11. public void addNewProduct(String name, double price) {
    12. Product p = new Product(name, price);
    13. products.put(p.getName(), p);
    14. // 通知观察者:
    15. observers.forEach(o -> o.onPublished(p));
    16. public void setProductPrice(String name, double price) {
    17. Product p = products.get(name);
    18. p.setPrice(price);
    19. // 通知观察者:
    20. observers.forEach(o -> o.onPriceChanged(p));
    21. }
    22. }

    就是这么一个小小的改动,使得观察者类型就可以无限扩充,而且,观察者的定义可以放到客户端:

    1. // observer:
    2. Admin a = new Admin();
    3. Customer c = new Customer();
    4. // store:
    5. Store store = new Store();
    6. // 注册观察者:
    7. store.addObserver(a);
    8. store.addObserver(c);

    用一张图画出观察者模式:

    1. ┌─────────┐ ┌───────────────┐
    2. Store │─ ─>│ProductObserver
    3. ┌─────┴─────┐
    4. ┌─────────┐ ┌─────────┐ ┌─────────┐
    5. Product Admin Customer ...
    6. └─────────┘ └─────────┘ └─────────┘

    观察者模式也有很多变体形式。有的观察者模式把被观察者也抽象出接口:

    1. public interface ProductObservable { // 注意此处拼写是Observable不是Observer!
    2. void addObserver(ProductObserver observer);
    3. void removeObserver(ProductObserver observer);
    4. }

    对应的实体被观察者就要实现该接口:

    有些观察者模式把通知变成一个Event对象,从而不再有多种方法通知,而是统一成一种:

    1. public interface ProductObserver {
    2. void onEvent(ProductEvent event);
    3. }

    让观察者自己从Event对象中读取通知类型和通知数据。

    广义的观察者模式包括所有消息系统。所谓消息系统,就是把观察者和被观察者完全分离,通过消息系统本身来通知:

    1. Messaging System
    2. ┌──────────────────┐
    3. ┌──┼>│Topic:newProduct │──┼─┐ ┌─────────┐
    4. └──────────────────┘ ├───>│ConsumerA
    5. ┌─────────┐ ┌──────────────────┐ └─────────┘
    6. Producer │───┼───>│Topic:priceChanged│────┘
    7. └─────────┘ └──────────────────┘
    8. ┌──────────────────┐ ┌─────────┐
    9. └──┼>│Topic:soldOut │──┼─────>│ConsumerB
    10. └──────────────────┘ └─────────┘

    消息发送方称为Producer,消息接收方称为Consumer,Producer发送消息的时候,必须选择发送到哪个Topic。Consumer可以订阅自己感兴趣的Topic,从而只获得特定类型的消息。

    此外,注意到我们在编写观察者模式的时候,通知Observer是依靠语句:

    这说明各个观察者是依次获得的同步通知,如果上一个观察者处理太慢,会导致下一个观察者不能及时获得通知。此外,如果观察者在处理通知的时候,发生了异常,还需要被观察者处理异常,才能保证继续通知下一个观察者。

    思考:如何改成异步通知,使得所有观察者可以并发同时处理?

    有的童鞋可能发现Java标准库有个java.util.Observable类和一个接口,用来帮助我们实现观察者模式。但是,这个类非常不!好!用!实现观察者模式的时候,也不推荐借助这两个东东。

    Store增加一种类型的观察者,并把通知改为异步。

    从下载练习:观察者模式练习 (推荐使用快速下载)

    观察者模式,又称发布-订阅模式,是一种一对多的通知机制,使得双方无需关心对方,只关心通知本身。