观察者模式


定义

观察者模式(Observer Pattern)也称发布订阅模式。

观察者模式的英文定义如下:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
意思是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

以生活中的例子来说,就像我们订阅报纸一样,每天有多少人订阅,当有新报纸发布的时候,就会有多少人收到新发布的报纸,这种模式就是订阅 - 发布模式,而报社和订阅者就满足定义中说是的,一对多的依赖关系。

“观察者模式” 这个词可能不太好理解,但如果用 “发布 — 订阅模式” 来替代的话,就相对好理解一些。

小贴士:本文会采用“观察者模式”来编写内容,但读者可以用“发布 - 订阅模式”来理解本文的内容,两者所说的是同一种模式。

组成角色

观察者模式包含如下角色:

  • 抽象主题(Subject)角色:该角色又称为 “发布者” 或” 被观察者 “,可以增加和删除观察者对象;
  • 具体主题(Concrete Subject)角色:该角色又称为 “具体发布者” 或 “具体被观察者”,它将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过(关联了观察关系)的观察者发出通知;
  • 抽象观察者(Observer)角色:该角色又称为 “订阅者”,定义一个接收通知的接口,在得到主题的通知时更新自己;
  • 具体观察者(Concrete Observer)角色:该角色又称为 “具体订阅者”,它会实现一个接收通知的方法,用来使自身的状态与主题的状态相协调。

角色之间的 UML 关系图如下:

观察者模式代码实现

抽象主题(发布者接口)

/**
 * 抽象主题(发布者接口)
 */
interface Subject {
    // 添加观察者(订阅者)
    public void attach(Observer o);
    // 删除观察者(订阅者)
    public void detach(Observer o);
    // 通知所有观察者(订阅者)
    public void notifyObservers();
}

具体主题(发布者)

/**
 * 具体主题(发布者)
 */
class ConcreteSubject implements Subject {
    // 存放观察者(订阅者)
    private List<Observer> list = new ArrayList<Observer>();
    @Override
    public void attach(Observer o) {
        // 添加观察者(订阅者)
        list.add(o);
    }
    @Override
    public void detach(Observer o) {
        // 删除观察者(订阅者)
        list.remove(o);
    }
    @Override
    public void notifyObservers() {
        // 通知所有观察者(订阅者)
        for (Observer o : list) {
            o.update();
        }
    }
    /**
     * 通知方法
     */
    public void change() {
        this.notifyObservers();
    }
}

抽象观察者(订阅者接口)

/**
 * 抽象观察者(订阅者接口)
 */
interface Observer {
    public void update();
}

具体观察者(订阅者)

/**
 * 具体观察者(订阅者)
 */
class ConcreteObserver implements Observer {
    @Override
    public void update() {
        // 主题有更新之后,执行的具体订阅(通知)方法
        System.out.println("我收到了通知~");
    }
}

客户端(调用)

/**
 * 观察者模式
 */
public class Client {
    public static void main(String[] args) {
        // 创建主题(发布者)
        ConcreteSubject subject = new ConcreteSubject();
        // 创建观察者(订阅者)
        Observer observer = new ConcreteObserver();
        // 关联订阅
        subject.attach(observer);
        // 改变主题(发布者)状态,发送通知
        subject.change();
    }
}

程序执行结果如下:

我收到了通知~

从以上代码可以看出,当主题(ConcreteSubject)的状态发生变化时,就会触发通知方法,通知方法会通知所有的观察者对象(ConcreteObserver),这样就完成了整个发布 — 订阅的过程。

优缺点

观察者模式的优点:

  • 观察者和被观察者之间,实现了抽象耦合。被观察者角色所知道的只是一个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展;
  • 此模式为广播模式,所有的观察者只需要订阅相应的主题,就能收到此主题下的所有广播。

观察者模式的缺点:

  • 观察者只知道被观察者会发生变化,但不知道何时会发生变化;
  • 如果主题之间有循环依赖,会导致系统崩溃,所以在使用时要特别注意此种情况;
  • 如果有很多个观察者,则每个通知会比较耗时。

应用场景

使用观察模式的典型应用场景如下:

  • 关联行为的场景,例如,在一个系统中,如果用户完善了个人资料,就会增加积分、添加日志、开放一些功能权限等,就比较适合用观察者模式;
  • 消息队列,例如,需要隔离发布者和订阅者,需要处理一对多关系的时候。

使用实例

以生活中的读者订阅为例,假设,读者 A 和 读者 B 订阅了某平台的图书,当有新的图书发布时就会给两位读者发送图书,实现代码如下。

读者接口和实现类

/**
 * 读者接口(订阅接口)
 */
interface IReader {
    public void update(String bookName);
}
/**
 * 读者类(订阅者)
 */
class Reader implements IReader {
    private String name;
    public Reader(String name) {
        this.name = name;
    }
    @Override
    public void update(String bookName) {
        System.out.println(name + "-收到了图书:" + bookName);
    }
}

平台接口和实现类

/**
 * 平台接口(发布方接口)
 */
interface IPlatform {
    public void attach(IReader reader);
    public void detach(IReader reader);
    public void notifyObservers(String bookName);
}
/**
 * 具体平台类(发布方)
 */
class Platform implements IPlatform {
    // 存放读者(订阅者)
    private List<IReader> list = new ArrayList();
    @Override
    public void attach(IReader reader) {
        // 添加读者(订阅者)
        list.add(reader);
    }
    @Override
    public void detach(IReader reader) {
        // 删除读者(订阅者)
        list.remove(reader);
    }
    @Override
    public void notifyObservers(String bookName) {
        // 通知所有读者(订阅者)
        for (IReader reader : list) {
            reader.update(bookName);
        }
    }
    /**
     * 通知方法
     */
    public void change(String bookName) {
        this.notifyObservers(bookName);
    }
}

客户端(调用)

public class Client {
    public static void main(String[] args) {
        // 创建图书平台(发布者)
        Platform platform = new Platform();
        // 创建读者 A(订阅者)
        Reader reader = new Reader("A");
        // 读者 A 订阅图书通知
        platform.attach(reader);
        // 创建读者 (订阅者)
        Reader reader2 = new Reader("B");
        // 读者 B 订阅图书通知
        platform.attach(reader2);
        platform.change("《Java面试全解析》");
    }
}

程序执行结果如下:

A - 收到了图书:《Java 面试全解析》

B - 收到了图书:《Java 面试全解析》

总结

观察者模式就是一个发布者对应多个订阅者的模式,发布者对应的角色就是主题(Subject),而订阅者对应的角色就是观察者(Observer),只要订阅者订阅了发布者(对象),当发布者的状态发生变化时,就会通知所有的订阅者。


Author: Re:0
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Re:0 !
 Previous
中介者模式 中介者模式
定义中介者?其实生活中大家再熟悉不过了这个词,我们熟悉的黄牛、房产中介等就是充当中介的角色,将我们的买票、购房等的需求自身消化再代为办理。又比如说中间件,马老师很忙,不能来一个人有事就直接找马老师对吧,所以要找一个中介,客户来了直接找中间人
2022-03-12
Next 
迭代器模式 迭代器模式
迭代器模式(Iterator Pattern)又称为游标(Cursor)模式,是最常被使用的几个模式之一,被广泛地应用到 Java 的 API 中。例如,Java 的集合(Collection)框架中,就广泛使用迭代器来遍历集合中的元素。
2022-03-11
  TOC