设计模式系列之观察者模式(Observer Pattern)

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

  • 如何解决:使用面向对象技术,可以将这种依赖关系弱化。

  • 关键代码:在抽象类里有一个 ArrayList 存放观察者们。

  • 应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

  • 优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

  • 缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

  • 使用场景:

    • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
    • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
    • 一个对象必须通知其他对象,而并不知道这些对象是谁。

需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

JDK中的API实现观察者模式

JDK自己实现了一系列的观察者API,我们可以利用他来实现我们的设计模式,后面我们自己模仿之实现这些操作.
主要是一个接口ObServe和一个类Obeservable,可观察者继承Observable ,观察者实现ObServe,然后观察者持有Observable来注册自己.
那么我们现在的需求是,软件开发人员向猎头注册自己,猎头有招聘消息的时候会通知在这里注册的软件开发人员,下面我们先实现猎头的代码

实现可观察者Observable(猎头)

package com.zhoutao123.design.pattern.observer.java;

import java.util.Observable;


/**
 * 猎头
 */
public class Headunting extends Observable {


    private String  invite = "请于明天到九华北路参加面试";

    public void setTime(String invite) {
        this.invite = invite;
        // 这句话非常重要,设置数据已经被改变
        setChanged();
        System.out.println("--------------------猎头发布消息--------------");
        // 推消息
        notifyObservers(invite);
    }
}

实现观察者Observer(软件开发人员)

观察者持有可被观察者对象,在构造方法中实现了向被观察者注册自己

package com.zhoutao123.design.pattern.observer.java;

import java.util.Observable;
import java.util.Observer;

/**
 * 前端开发
 */
public class ApplicantWithFe implements Observer {
    // 持有Observable 方便取消注册
    private Observable observable;

    public ApplicantWithFe(Observable observable) {
        this.observable = observable;
        // 向被观察者注册自己
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(arg instanceof  String){
            System.out.println("我是前端开发工程师,我已经接受到面试邀请---->" + (String)arg );
        }
    }

    // 取消注册自己
    public void unregrester(){
        System.out.println("我是前端工程师,目前已经就业,取消注册");
        observable.deleteObserver(this);
    }
}
package com.zhoutao123.design.pattern.observer.java;

import java.util.Observable;
import java.util.Observer;

/**
 * Go语言开发工程师
 */
public class ApplicantWithGo implements Observer {
    // 持有Observable 方便取消注册
    private Observable observable;

    public ApplicantWithGo(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(arg instanceof  String){
            System.out.println("我是Go开发工程师,我已经接受到面试邀请---->" + (String)arg );
        }
    }

    public void unregrester(){
        System.out.println("我是GO工程师,目前已经就业,取消注册");
        observable.deleteObserver(this);
    }
}

package com.zhoutao123.design.pattern.observer.java;

import java.util.Observable;
import java.util.Observer;

/**
 * Java 开发工程师
 */
public class ApplicantWithJava implements Observer {

    // 持有Observable 方便取消注册
    private Observable observable;

    public ApplicantWithJava(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(arg instanceof  String){
            System.out.println("我是Java开发工程师,我已经接受到面试邀请---->" + (String)arg );
        }
    }

    public void unregrester(){
        System.out.println("=============> 我是Java工程师,目前已经就业,取消注册");
        observable.deleteObserver(this);
    }
}

目前我们使用是push模式,即ObservableObserver推送数据,后面我们自己实现的代码中使用了pull模式,即Observable通知Observer数据已经改变,然后Observer通过Observable获取数据.

测试代码

创建Observable(Headunting)和若干个Observer(ApplicantWithJava/Go/Fe)
然后Observable(Headunting) 设置消息,观察者就会接到数据,输出效果.

package com.zhoutao123.design.pattern.observer.java;

public class Test {

    public static void main(String[] args) {
        // 创建猎头
        Headunting headunting = new Headunting();

        // 创建若干应聘者
        ApplicantWithJava applicantWithJava = new ApplicantWithJava(headunting);
        ApplicantWithFe applicantWithFe = new ApplicantWithFe(headunting);
        ApplicantWithGo applicantWithGo = new ApplicantWithGo(headunting);


        // 猎头发布招聘消息
        headunting.setTime("后天10:00 到安徽合肥九华北路171号参加面试");

        //Java 工程师找到了工作退出招聘
        applicantWithJava.unregrester();

        headunting.setTime("大后天15:00 到江苏南京九华北路181号参加面试");


    }
}

测试输出

可以看到,猎头发布消息后,已经注册的用户会接受到消息,然后Java工程师推出注册,然后猎头再次发送消息后,只有两个人接受到消息.

--------------------猎头发布消息--------------
我是Go开发工程师,我已经接受到面试邀请---->后天10:00 到安徽合肥九华北路171号参加面试
我是前端开发工程师,我已经接受到面试邀请---->后天10:00 到安徽合肥九华北路171号参加面试
我是Java开发工程师,我已经接受到面试邀请---->后天10:00 到安徽合肥九华北路171号参加面试

=============> 我是Java工程师,目前已经就业,取消注册

--------------------猎头发布消息--------------
我是Go开发工程师,我已经接受到面试邀请---->大后天15:00 到江苏南京九华北路181号参加面试
我是前端开发工程师,我已经接受到面试邀请---->大后天15:00 到江苏南京九华北路181号参加面试

自定义实现观察者模式

自定义实现观察者模式的代码中,我们首先实现两个基本的接口,和上面的实现方法类型类似.这里说明一下我们的需求,老师作为可观察者,学生做为观察者,当老师发布数据的时候,学生根据自己的实现来实现不同的计算功能.

下面我们开始从头开始实现代码:

可观察者接口

package com.zhoutao123.design.pattern.observer.self;

public interface SelfObservable {

    /**
     * 获取数据
     *
     * @return
     */
    NumberData getData();

    /**
     * 通知所有Observer
     * 此处需过需要不同的过滤模式,可是使用之前学过的过滤器模式实现
     */
    void notifyObservers();

    /**
     * 增加观察者
     * @param observer
     */
    void addObserver(SelfObserver observer);


    /**
     * 移除观察者
     * @param observer
     */
    void removeObserver(SelfObserver observer);

}

观察者接口

package com.zhoutao123.design.pattern.observer.self;

public interface SelfObserver {

    /**
     * 更新数据,这里和JDK实现的不同,是PULL,拉数据
     */
    void update();

    /**
     * 取消注册
     */
    void unregister();

}

具体实现

首先实现数据的模型,很简单的JavaBean对象


package com.zhoutao123.design.pattern.observer.self;

public class NumberData {

    private Integer num1;

    private Integer num2;

    public NumberData(Integer num1, Integer num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    // 省略Setter/Getter方式
}

首先是老师,老师实现了可观察者接口

package com.zhoutao123.design.pattern.observer.self;

import java.util.ArrayList;
import java.util.List;

public class Teacher implements SelfObservable {

    // 注册的学生集合
    List<SelfObserver> observers;

    // 持有的数据
    NumberData numberData;

    // 初始化自己持有的数据
    public Teacher() {
        this.observers = new ArrayList<>();
        numberData = new NumberData(0,0);
    }

    // 获取当前数据
    @Override
    public NumberData getData() {
        return numberData;
    }

    // 更新数据,注意更新数据的时候调用notifyObservers用于通知所有的观察者
    // 如果需要多种过滤方法,可以尝试之前学习的过滤器模式
    public void setNumberData(NumberData numberData) {
        this.numberData = numberData;
        System.out.println("----------------老师发布了新数据----------------");
        notifyObservers();
    }

    // 遍历,调用每个观察者的update方法
    @Override
    public void notifyObservers() {
        for (SelfObserver observer : observers) {
            observer.update();
        }
    }

    // 新增观察者
    @Override
    public void addObserver(SelfObserver selfObserver) {
        observers.add(selfObserver);
    }

    // 取消观察者
    @Override
    public void removeObserver(SelfObserver selfObserver) {
        observers.remove(selfObserver);
    }
}

学生的实现代码,学生写了3个学生,在接受到数据之后,分别会计算,和差以及乘积,然后输出.

package com.zhoutao123.design.pattern.observer.self;

public class StudentA implements SelfObserver {

    SelfObservable selfObservable;

    // 初始化的时候,开始持有可观察者
    public StudentA(SelfObservable selfObservable) {
        this.selfObservable = selfObservable;
        selfObservable.addObserver(this);
    }

    @Override
    public void update() {
        // 这里是拉数据,从可观察者哪里获取数据,而不是可观察者在调用update方法时候push过来的
        NumberData data = selfObservable.getData();
        System.out.println(String.format("接受到老师发过来的数据:%d ,%d 计算他们的和是:%d",data.getNum1(),data.getNum2(),data.getNum1() + data.getNum2()));
    }

    // 取消自己的注册
    @Override
    public void unregister() {
        selfObservable.removeObserver(this);
    }
}
package com.zhoutao123.design.pattern.observer.self;

public class StudentB implements SelfObserver {

    SelfObservable selfObservable;

    public StudentB(SelfObservable selfObservable) {
        this.selfObservable = selfObservable;
        selfObservable.addObserver(this);
    }

    @Override
    public void update() {
        NumberData data = selfObservable.getData();
        System.out.println(String.format("接受到老师发过来的数据:%d ,%d 计算他们的差是:%d",data.getNum1(),data.getNum2(),data.getNum1() - data.getNum2()));
    }

    @Override
    public void unregister() {
        selfObservable.removeObserver(this);
    }
}
package com.zhoutao123.design.pattern.observer.self;

public class StudentC implements SelfObserver {

    SelfObservable selfObservable;

    public StudentC(SelfObservable selfObservable) {
        this.selfObservable = selfObservable;
        selfObservable.addObserver(this);
    }

    @Override
    public void update() {
        NumberData data = selfObservable.getData();
        System.out.println(String.format("接受到老师发过来的数据:%d ,%d 计算他们的乘积是:%d",data.getNum1(),data.getNum2(),data.getNum1() * data.getNum2()));
    }

    @Override
    public void unregister() {
        selfObservable.removeObserver(this);
    }
}

测试代码

package com.zhoutao123.design.pattern.observer.self;

public class Test {

    public static void main(String[] args) {
        // 创建老师
        Teacher teacher = new Teacher();

        // 创建学生
        StudentA a = new StudentA(teacher);
        StudentB b = new StudentB(teacher);
        StudentC c = new StudentC(teacher);


        //老师发布数据
        teacher.setNumberData(new NumberData(12,6));


        // 一名学生走了
        c.unregister();

        teacher.setNumberData(new NumberData(2,6));


        // 又走一个学生
        a.unregister();

        teacher.setNumberData(new NumberData(1,3));
    }
}

测试效果

这里也就很简单了,自己尝试这分析一下

----------------老师发布了新数据----------------
接受到老师发过来的数据:12 ,6 计算他们的和是:18
接受到老师发过来的数据:12 ,6 计算他们的差是:6
接受到老师发过来的数据:12 ,6 计算他们的乘积是:72
----------------老师发布了新数据----------------
接受到老师发过来的数据:2 ,6 计算他们的和是:8
接受到老师发过来的数据:2 ,6 计算他们的差是:-4
----------------老师发布了新数据----------------
接受到老师发过来的数据:1 ,3 计算他们的差是:-2

总结

通过这两个例子,详细自己对观察者模式有了更深的理解,自己总结了一些步骤,如下:

  • 创建可观察者
  • 创建观察者
  • 向可观察者中注册观察者(这一步可以在创建观察者的时候执行)
  • 可观察更新数据,使用notifyObserver()通知所有的观察者
  • 观察者接受到请求,获取数据(pull/push),然后分别执行自己的逻辑
上一篇:设计模式-观察者模式


下一篇:Mobx-React : 当前适合React的状态管理工具