观察者模式详解

本文来说下观察者模式


定义

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

观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用于在关联行为之间建立一套触发机制的场景。观察者模式在现实生活应用也非常广泛,比如:微信朋友圈动态通知、GPser生态圈消息通知、邮件通知、广播通知、桌面程序的事件响应等(如下图)。

在这里插入图片描述


介绍

  • 观察者属于行为型模式。
  • 观察者模式又被称作发布/订阅模式。
  • 观察者模式主要用来解耦,将被观察者和观察者解耦,让他们之间没有没有依赖或者依赖关系很小

UML类图

在这里插入图片描述

角色说明:

  • Subject(抽象主题):又叫抽象被观察者,把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject(具体主题):又叫具体被观察者,将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
  • Observer (抽象观察者) :为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  • ConcrereObserver(具体观察者):实现抽象观察者定义的更新接口,当得到主题更改通知时更新自身的状态。

代码实现

以送快递为例,快递员有时只是把快递拉到楼下,然后就通知收件人下楼去取快递

创建抽象观察者

定义一个接到通知的更新方法,即收件人收到通知后的反应:

package cn.wideth.observer;

//抽象观察者
public interface Observer {

    //更新方法
    void update(String message);

}

创建具体观察者

实现抽象观察者中的方法,这里创建两个类,一个男孩类和一个女孩类,定义他们收到通知后的反应:

package cn.wideth.observer;

public class Boy implements Observer{

    private String name;//名字

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

    @Override
    public void update(String message) {//男孩的具体反应

        System.out.println(name + ",收到了信息:" + message+"屁颠颠的去取快递.");
    }
}
package cn.wideth.observer;

public class Girl implements Observer{

    private String name;//名字

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

    @Override
    public void update(String message) {//女孩的具体反应

        System.out.println(name + ",收到了信息:" + message+"让男朋友去取快递~");
    }
}

创建抽象主题

即抽象被观察者,定义添加,删除,通知等方法:

package cn.wideth.observer;

//抽象被观察者
public interface  Observable {

    //添加观察者
    void add(Observer observer);

    //删除观察者
    void remove(Observer observer);

    //通知观察者
    void notify(String message);
}

创建具体主题

即具体被观察者,也就是快递员,派送快递时根据快递信息来通知收件人让其来取件:

package cn.wideth.observer;

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

//快递员
public class Postman implements  Observable{

    //保存收件人(观察者)的信息
    private List<Observer> personList = new ArrayList<>();

    @Override
    public void add(Observer observer) {//添加收件人
        personList.add(observer);
    }

    @Override
    public void remove(Observer observer) {//移除收件人
        personList.remove(observer);

    }

    @Override
    public void notify(String message) {//逐一通知收件人(观察者)

        for (Observer observer : personList) {
            observer.update(message);
        }
    }
}

客户端测试

package cn.wideth.observer;

public class Main {

    public static void main(String[] args) {

        Observable postman = new Postman();

        Observer boy1 = new Boy("李天宇");
        Observer boy2 = new Boy("洪海洋");
        Observer girl1 = new Girl("唐小美");
        Observer girl2 = new Girl("张小小");

        postman.add(boy1);
        postman.add(boy2);
        postman.add(girl1);
        postman.add(girl2);

        postman.notify("快递到了,请下楼领取,");
    }
}

程序测试结果

在这里插入图片描述


JDK内部使用

实际上,JDK内部也内置了Observable(抽象被观察者),Observer(抽象观察者)这两个类,我们也可以直接拿来用,其代码如下:

public interface Observer {//(抽象观察者
    //只定义了一个update方法
    void update(Observable o, Object arg);
}

public class Observable {//抽象被观察者

    private boolean changed = false;//定义改变状态,默认为false
    private final ArrayList<Observer> observers;//定义一个观察者list

    public Observable() {//构造函数,初始化一个观察者list来保存观察者
        observers = new ArrayList<>();
    }
    //添加观察者,带同步字段的,所以是线程安全的
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    //删除观察者
    public synchronized void deleteObserver(Observer o) {
        observers.remove(o);
    }

    //通知所以观察者,无参数
    public void notifyObservers() {
        notifyObservers(null);
    }

     //通知所有观察者,带参数
    public void notifyObservers(Object arg) {

        Observer[] arrLocal;
        //加synchronized字段,保证多线程下操作没有问题
        synchronized (this) {
            if (!hasChanged())//这里做了是否发生改变的判断,是为了防止出现无意义的更新
                return;
                
            //ArrayList转换成一个临时的数组,这样就防止了通知,添加,移除同时发生可能导致的异常
            arrLocal = observers.toArray(new Observer[observers.size()]);
            clearChanged();///清除改变状态,设置为false
        }
        //遍历逐一通知
        for (int i = arrLocal.length-1; i>=0; i--)
            arrLocal[i].update(this, arg);
    }

    //清楚所有观察者
    public synchronized void deleteObservers() {
        observers.clear();
    }

    //设置被观察者为改变状态,设置为true
    protected synchronized void setChanged() {
        changed = true;
    }

    //清除改变状态,设置为false
    protected synchronized void clearChanged() {
        changed = false;
    }

    //返回当前的改变状态
    public synchronized boolean hasChanged() {
        return changed;
    }

    //观察者数量
    public synchronized int countObservers() {
        return observers.size();
    }
}

应用场景

  • 当一个对象的改变需要通知其它对象改变时,而且它不知道具体有多少个对象有待改变时。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制

优点

  • 解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
  • 易于扩展,对同一主题新增观察者时无需修改原有代码。

缺点

  • 依赖关系并未完全解除,抽象主题仍然依赖抽象观察者。
  • 使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
  • 可能会引起多余的数据通知。

基于Guava API轻松落地观察者模式

在这里,我还推荐给大家一个实现观察者模式非常好用的框架。API使用也非常简单,举个例子,先引入maven依赖包:

maven导入

<dependency>
  <groupId>com.google.guava</groupId> 
  <artifactId>guava</artifactId> 
  <version>20.0</version> 
</dependency>

创建侦听事件GuavaEvent:

package cn.wideth.guava;

import com.google.common.eventbus.Subscribe;

public class GuavaEvent {

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

客户端测试代码:

package cn.wideth.guava;

import com.google.common.eventbus.EventBus;

public class GuavaEventTest {

    public static void main(String[] args) {

        //消息总线
        EventBus eventBus = new EventBus();
        GuavaEvent guavaEvent = new GuavaEvent();
        eventBus.register(guavaEvent);
        eventBus.post("Tom");

        //从Struts到SpringMVC的升级
        //因为Struts面向的类,而SpringMVC面向的是方法

        //前面两者面向的是类,Guava面向是方法

        //能够轻松落地观察者模式的一种解决方案
        //MQ
    }
}

测试程序结果

在这里插入图片描述


本文小结

本文详细介绍了观察者模式的信息,观察者模式是一种应用非常普遍的设计模式,通常被用在程序来对程序进行解耦。在开源的框架或者中间件中也有比较多的应用,比如在spring框架和zookeeper框架中都起着重要的作用。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值