观察者模式

定义

又叫发布-订阅模式、模型-视图模式、源-监听器模式或从属者模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者同时监听,使得每当主题对象的状态变化时,所有依赖于它的对象都回得到同事并自动更新。属于行为型模式

观察者模式的核心是将观察者与被观察者解耦,以类似于消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。

适用情景

在软件系统中,当系统一方行为依赖于另一方行为的变动时,可以适用观察者模式松耦合联动双发,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。如闹钟,app角标通知,邮件通知,桌面程序的事件响应。

  1. 当一个抽象模型包含两个方面内容,其中一个方面依赖于另一个方面。
  2. 其他一个或多个对象的变化依赖与另一个对象的变化;
  3. 实现类似于广播机制的功能,无需知道具体的收听者,只需要分发广播,系统中感兴趣的对象会自动接受该广播
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知

角色

  1. 抽象主题(Subject):指被观察的对象。该角色是一个抽象类或接口,定义了增加,删除,通知观察者对象的方法。
  2. 具体主题(ConcreteSubject):具体被观察者,当其内状态发生变化时,会通知已注册的观察者。
  3. 抽象观察者(Observer):定义了响应通知的更新方法。
  4. 具体观察者(ConcreteObserver):在得到状态更新是,会自动做出响应。

实例

public class Question {
    private String userName;
    private String content;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

public class GPer extends Observable {
    private String name = "GPer生态";
    private static  GPer gPer = null;

    private GPer() {
    }

    public static GPer getInstance(){
        if (null == gPer) {
            gPer = new GPer();
        }
        return gPer;
    }

    public String getName() {
        return name;
    }

    public void publishQuestion(GPer gPer, Question question) {
        System.out.println(String.format("%s 在 %s 上提交了一个问题",question.getUserName(), this.name));
        setChanged();
        notifyObservers(question);
    }

public class Teacher  implements Observer {

    private String name;

    public Teacher(String name) {
        this.name = name;
    }


    public void update(Observable observable, Object o) {
        GPer gPer = (GPer) observable;
        Question question = (Question) o;
        System.out.println(String.format("%s 老师,你好!\n 您收到了一个来自 %s 的提问,内容如下:\n %s \n 提问者:%s",
                name, gPer.getName(), question.getContent(), question.getUserName()));
    }
}

public class Test {
    public static void main(String[] args) {
        GPer gPer = GPer.getInstance();
        Teacher tom = new Teacher("Tom");
        gPer.addObserver(tom);
        Question question = new Question();
        question.setUserName("小明");
        question.setContent("观察者使用的场景");

        gPer.publishQuestion(gPer,question);
    }
}

观察者框架Guave

public class GuavaEvent {

    public void subscribe(String str){
        System.out.println(String.format("执行subscribe方法,传入的参数是:%s",str));
    }
}

public class Test {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        GuavaEvent guavaEvent = new GuavaEvent();
        eventBus.register(guavaEvent);
        eventBus.post("Tom");
    }
}

观察者模式在源码中的使用

Spring中的ContextLoaderListener实现了ServletContextListener接口,而ServletContextListener接口又继承了EventListener,在JDK中EventListener使用范围广泛。

优点

  1. 观察者与被观察者是松耦合(抽象耦合)的,符合依赖导致原则;
  2. 分离了表示层(观察者)和数据逻辑层(被观察者),并创建了一套触发机制,使得数据的变化可以响应到多个表示层上;
  3. 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观测者触发事件时,只有感兴趣的观察者可以接收到通知。

缺点

  1. 如果观察者数量过多,则事件通知或耗时较长;
  2. 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接手该事件;
  3. 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,到时系统崩溃。