设计模式之观察者模式相关、监听器模式

观察者模式相关

一、观察者模式

概念

观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自己更新自己。(在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新)
在这里插入图片描述

结构图

观察者模式结构图:

在这里插入图片描述

Subject类,可以翻译为主题或者抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加、删除观察者对象 以及通知聚集里面的观察者。

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体现察者对象中;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

Observer类,抽象观察者,为所有的具体观察者定义一个接口, 在得到主题的通知时更新自己。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,用来更新自己。

ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使自身的状态与主题的状态相协调。具体观察者角色通常用一个具体子类实现。

代码

抽象通知者:
/**
 * 主题(抽象通知者)
 */
abstract class Subject {
    private List<Observer> observerList = new ArrayList<>();

    /**
     * 增加观察者
     */
    public void attach(Observer observer) {
        observerList.add(observer);
    }

    /**
     * 移除观察者
     */
    public void detach(Observer observer) {
        observerList.remove(observer);
    }

   
    /**
     * 通知监听该主题的所有观察者
     */
    public void notifyAllObserver(Object msg) {
        observerList.forEach(observer -> observer.update(msg));
    }
}
具体通知者:
/**
 * 具体主题(具体通知者)
 */
public class ConcreteSubject extends Subject {

    /**
     * 具体主题状态
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
    }
}
抽象观察者:
/**
 * 抽象观察者
 */
public interface Observer {

    void update(Object msg);
}
具体观察者:
/**
 * 具体观察者
 */
public class ConcreteObserver implements Observer {

    private String name;

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

    @Override
    public void update(Object msg) {
        // 业务逻辑实现
        System.out.println("观察者:"+ name + ",接收到主题的消息: " + msg);
    }

    // get set
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
客户端:
/**
 * 观察者模式-客户端
 */
public class Client {

    public static void main(String[] args) {
        // 主题
        ConcreteSubject subject = new ConcreteSubject();

        // 为主题添加观察者
        subject.attach(new ConcreteObserver("1号"));
        subject.attach(new ConcreteObserver("2号"));
        subject.attach(new ConcreteObserver("3号"));

        // 主题的状态的发生变化,通知所有的观察者
        // subject.setSubjectState("007"); 
        subject.notifyAllObserver("主题的状态发生变化了!");
    }
}

---------输出----------
观察者:1,接收到主题的消息: 主题的状态发生变化了!
观察者:2,接收到主题的消息: 主题的状态发生变化了!
观察者:3,接收到主题的消息: 主题的状态发生变化了!

特点

什么时候考虑使用观察者模式呢?

  • 当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
  • 当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

总的来讲,观察者模式所做的工作其实就是在降低耦合(并没有完全解耦)。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。(可以说是:依赖倒转原则的最佳体现)

  • 依赖倒转原则:
    • 高层模块不应该依赖低层模块,两个都应该依赖抽象
    • 抽象不应该依赖细节,细节应该依赖抽象

用观察者模式的动机是什么呢?

  • 将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
  • 而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦 Subject的状态发生了改变,所有的Observer都可以得到通知。Subject 发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。

二、监听器模式

监听器模式可以理解为是观察者模式的另一种形态。

监听器模式通常包含三个角色:

  • 事件源

  • 事件对象

  • 事件监听器

如果观察者模式中的类名和方法对照改一下,并不改变业务逻辑,我们来看看是啥效果。

比如:

  • 将Subject改名为EventSource
  • 将Observer改名为EventListener,将其update方法改为onClick(),参数由object改为Event(事件对象)

在这里插入图片描述

事件对象Event:

定义一个事件对象Event,用来传递事件信息:

public class Event {
    private String data;
    private String type;

    public Event(String data, String type) {
        this.data = data;
        this.type = type;
    }
    // 省略getter/setter
}

EventSource(事件源)对应着通知者:

/**
 * 事件源
 */
public class EventSource {

    private List<EventListener> listeners = new ArrayList<>();

    /**
     * 增加监听者
     */
    public void attach(EventListener listener) {
        listeners.add(listener);
    }

    /**
     * 移除监听者
     */
    public void detach(EventListener listener) {
        listeners.remove(listener);
    }

    /**
     * 触发,通知所有的监听者
     */
    public void notifyAll(Event event) {
        for (EventListener listener : listeners) {
            listener.onClick(event);
        }
    }
} 

EventListener(事件监听者)对应着抽象观察者:

/**
 * 事件监听者
 */
public interface EventListener {
    void onClick(Event event);
}
ConcreteListener(具体的事件监听者实现类)对应具体观察者:
/**
 * 具体的事件监听者实现类
 */
public class ConcreteListener implements EventListener{
    private String name;

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

    @Override
    public void onClick(Event event) {
        System.out.println("监听者:"+ name +",触发事件,type:" + event.getType() + ",data:" + event.getData());
    }

    // get set
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

客户端:

/**
 * 监听者模式-客户端
 */
public class Client {

    public static void main(String[] args) {
        // 事件监听器
        EventListener listenerA = new ConcreteListener("A");

        // 事件源
        EventSource eventSource = new EventSource();
        // 监听器A 监听事件源
        eventSource.attach(listenerA);
        eventSource.notifyAll(new Event("eventDataA", "eventType"));
    }
}

通过上面的对照代码,我们可以看出,即便业务逻辑不变,经过重命名的观察者模式已经变为监听器模式了。而它们的对照关系是:事件源对照Subject(主题)、事件对象对照update方法的Object、事件监听器对照ConcreteObserver(订阅者)。这也再次证明了所说的“监听器模式是观察者模式的另一种形态”。

三、观察者模式和监听器模式对比

用一张图,来比较观察者模式和监听器模式的联系和区别:

在这里插入图片描述

相同之处:监听者模式和观察者模式都可以实现在某件事情状态发生改变时,触发后续要做的事情,在功能和实现上都比较类似,且都做到了低耦合,弱依赖。

不同之处:

  • 监听者模式通过引入事件对象,充当事件的载体,可以传递更多的消息,可以更灵活的实现不同的事件触发不同的监听器,执行不同的逻辑。相比观察者模式,监听者模式的适用场景更广,但实现起来比观察者模式略复杂一点。
  • 监听者模式适用于事件类型超过1种且需要区别对待的场景,比如监听鼠标的操作:鼠标的点击、鼠标移动、鼠标拖拽等…
  • 观察者模式适用于类似发布和订阅这种场景,当有发布内容时,将发布内容推送给订阅者即可,无需区分事件类型来区别对待。

四、观察者模式与发布订阅模式

观察者模式:其实是为了实现松耦合。在Subject的抽象类里维护了一个观察者(Observer)的集合,以此来实现通知。但依然存在耦合。

发布订阅模式:完全不存在耦合关系。

都觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。

其实并不是。

  • 在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识,它们是通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。
  • 在发布订阅模式里,订阅方,可以采用主动拉取、被动接收(队列推送),这两种方式来获取数据。
    在这里插入图片描述

总结:

  • 从表面上看:

    • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
    • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
  • 往更深层次讲:

    • 观察者模式,是松耦合的关系
    • 发布订阅模式,则完全不存在耦合
  • 从使用层面上讲:

    • 观察者模式,多用于单个应用内部
    • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件kafka

五、Spring中的最佳实践

Spring事件机制是观察者模式的一种实现:

  • 事件源
  • 事件监听者
  • 事件对象
  • 事件广播器ApplicationEventMulticaster,负责把事件转发给监听者,使事件源与事件监听者实现了完全解耦。

工作流程如下:

  • 事件源调用applicationEventPublisher.publishEvent(msg);将事件发送给了ApplicationEventMulticaster,然后由ApplicationEventMulticaster注册所有的 Listener(监听者),最后根据事件类型决定转发给哪个Listener。

在这里插入图片描述

在这里插入图片描述

Spring中观察者模式包含四个角色:事件、事件源、事件监听器、事件管理。

事件:ApplicationEvent是所有事件对象的父类。ApplicationEvent继承自jdk的EventObject,所有的事件都需要继承ApplicationEvent,并且通过source得到具体事件。Spring 提供了很多内置事件,比如:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent。

事件源:ApplicationContext,ApplicationContext是Spring的核心容器,在事件监听中ApplicationContext可以作为事件的发布者,也就是事件源。因为ApplicationContext继承自ApplicationEventPublisher。在ApplicationEventPublisher中定义了事件发布的方法:publishEvent(Object event),该方法会获取一个多播器,然后去发布事件,调用事件监听器。让其执行监听的逻辑。

事件监听器:ApplicationListener,也就是观察者,继承自jdk的EventListener,该类中只有一个方法onApplicationEvent,当监听的事件发生后该方法会被执行。

事件管理:ApplicationEventMulticaster,用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把ApplicationContext发布的Event广播给它的监听器列表。

代码:

ApplicationEvent的实现类-OrderEvent:
package com.wlw.share.practice.event;

import org.springframework.context.ApplicationEvent;

/**
 * 自定义事件
 */
public class OrderEvent extends ApplicationEvent {

    private String name;

    public OrderEvent(Object source, String name) {
        super(source);
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
ApplicationListener的实现类-OrderEventListener:
package com.wlw.share.practice.eventlistener;

import com.wlw.share.practice.event.OrderEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 自定义的事件监听器
 */
@Component
public class OrderEventListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        if ("sell".equals(event.getName())) {
            System.out.println("卖出,减库存.......");
        }
    }
}
测试

事件源采用spring 自带的 - AnnotationConfigApplicationContext

package com.wlw.share.practice;


import com.wlw.share.practice.config.MainConfig;
import com.wlw.share.practice.event.OrderEvent;
import com.wlw.share.practice.pojo.Order;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 客户端
 */
public class Client {

    public static void main(String[] args) {

        // 事件源
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

        Order order = new Order();
        System.out.println("下单");


        // 发布订单事件 -> 减库存
        context.publishEvent(new OrderEvent(order, "sell"));

    }
}

六、总结

对于观察者模式,应用最多的还是其思想,直接使用的不多,基本上都是在基础上进行改动,把松耦合关系变成完全解耦,例如:

  • 消息队列Kafka的设计,增加一个Broker来充当第三方,进行接收和转发消息。
  • Spring 的监听器模式,用ApplicationEventMulticaster,来注册监听器、广播事件,使事件源与事件监听器完全解耦。
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值