每天认识一个设计模式-观察者模式:事件驱动架构的基石

一、前言

在分布式系统开发中,我们经常会遇到这样的场景:订单服务完成支付后需要通知库存服务扣减库存,用户状态变更时需要同步更新多个关联系统的缓存,甚至是 GUI 界面上按钮状态需要随数据模型动态变化。传统的硬编码调用方式会导致模块间耦合度飙升,牵一发而动全身的修改成本让开发者苦不堪言。观察者模式作为事件驱动架构的核心基石,通过建立对象间的动态依赖关系,让 “数据变化通知” 这一通用需求有了优雅的解决方案。下面让咱们一起来好好了解下~

二、观察者模式原型设计及说明

观察者模式(Observer Pattern)是一种行为型设计模式,作为设计模式家族中的关键成员,在实现对象间解耦这一关键目标上发挥着无可替代的战略作用。它巧妙地定义了对象之间一种松耦合的一对多依赖关系,当被观察对象(Subject)的状态发生变化时,所有依赖于它的观察对象(Observer)都会自动收到通知,并能够根据自身需求进行相应的更新操作 。​

3.1UML 类图介绍

下面我们结合类图一起来研究一下这个模式的设计方式:

这里我们可以看到在观察者模式中,最主要的就是上面四个核心角色:

Subject(目标):它是一个抽象类或接口,定义了一系列方法,用于管理观察者对象的注册和移除,以及在自身状态发生变化时通知观察者。它是观察者模式的核心,维护着观察者列表,并不关心具体的观察者是谁以及它们如何处理通知,只负责将通知传递给所有注册的观察者,起到了抽象和规范的作用 。​

Observer(观察者):同样是一个抽象类或接口,定义了一个更新方法update。当目标对象的状态发生改变并发出通知时,所有注册的观察者的update方法会被调用,观察者通过这个方法来获取目标的状态并执行相应的操作,但它不了解目标的具体实现细节,只关注自身的更新逻辑 。​

ConcreteSubject(具体目标):是Subject的具体实现类,包含了实际的业务逻辑和状态。它负责维护观察者列表,实现添加、移除观察者以及通知观察者的方法。当它的状态发生变化时,会调用notifyObservers方法,通知所有注册的观察者,并且可以根据需要提供获取和设置自身状态的方法 。​

ConcreteObserver(具体观察者):实现了Observer接口,拥有自己的状态和行为。在update方法中,它会根据具体目标的状态变化,执行特定的操作,这些操作通常与自身的业务需求相关。它与具体目标之间存在关联,通过update方法接收具体目标的通知并进行处理 。

这四个角色紧密协作,Subject和Observer定义了抽象的接口规范,ConcreteSubject和ConcreteObserver则基于这些规范实现具体的业务逻辑,它们之间的松耦合关系使得系统具有良好的扩展性和维护性。

3.2观察者模式中数据传递的策略剖析:推模型与拉模型

观察者模式在实际应用中,当目标对象(Subject)状态改变时,需要将状态信息传递给观察者(Observer),此时就产生了两种不同的信息传递方式,即推模型和拉模型。这两种模型是为了满足不同场景下数据传递的需求而设计的,它们在数据的发送和接收方式上有所不同,从而衍生出了不同的使用特性和适用场景。

推模型(Push Model)

这种方式是当目标对象的状态发生变化时,目标对象会主动将完整的状态数据推送给所有已注册的观察者。也就是说,目标对象不仅通知观察者状态发生了变化,还直接把相关的状态数据一同发送过去,观察者无需主动去获取数据,被动接收即可。

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

// 观察者接口
interface Observer {
    void update(String newState);
}

// 目标对象接口
interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers(String newState);
}

// 具体目标对象
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String newState) {
        this.state = newState;
        for (Observer o : observers) {
            o.update(newState); // 直接将新状态数据推送给观察者
        }
    }

    public String getState() {
        return state;
    }
}

// 具体观察者
class ConcreteObserver implements Observer {
    private String name;

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

    @Override
    public void update(String newState) {
        System.out.println(name + " 接收到新状态: " + newState);
    }
}
#  测试
public class Main {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();

        ConcreteObserver observer1 = new ConcreteObserver("Observer1");
        ConcreteObserver observer2 = new ConcreteObserver("Observer2");

        subject.attach(observer1);
        subject.attach(observer2);

        subject.notifyObservers("状态已变更为新状态");
    }
}

整个推模型的工作流程如下:

  1. 创建 ConcreteSubject 目标对象和多个 ConcreteObserver 观察者对象。
  2. 调用 ConcreteSubject 的 attach 方法将观察者注册到目标对象中。
  3. 当目标对象的状态发生变化时,调用 ConcreteSubject 的 notifyObservers 方法,并传入新状态数据。
  4. notifyObservers 方法会遍历所有已注册的观察者,调用它们的 update 方法,将新状态数据推送给观察者。
  5. 观察者的 update 方法接收到数据后进行相应的处理。

 这种方式的优点在于:

  1. 使用简单:对于观察者而言,实现起来较为轻松。它只需要实现一个接收数据的方法(如update方法),等待目标对象推送数据过来并进行处理就行,不需要额外去考虑如何获取数据。
  2. 实时性高:特别适合那些对实时性要求较高且数据量相对较小的场景。因为目标对象主动推送,所以观察者能够迅速得到最新的状态信息,及时做出响应。

而相对应的也会存在如下问题

  • 可能推送无关数据:目标对象在推送数据时,可能会包含一些观察者并不关心的信息。例如,目标对象的状态数据包含了多个属性,但某个观察者只关注其中一个属性的变化,此时其他无关属性的数据也会被推送给该观察者,造成了不必要的数据传输和资源浪费。
  • 增加网络传输压力:在分布式系统中,如果目标对象有很多已注册的观察者,并且频繁地推送大量数据,那么网络传输的负担会显著增加,可能影响整个系统的性能。

用一个天气信息系统来说,天气数据采集中心就可以作为目标对象,而各个用户终端则代表观察者。当天气数据(如温度、湿度、风力等)发生变化时,采集中心主动将所有的天气数据推送给各个用户终端。用户终端只需要接收这些数据并进行展示即可,无需自己去获取。但可能存在的问题是,有些用户可能只关心温度信息,而采集中心推送的包含湿度、风力等其他数据对这些用户来说是多余的。 

拉模型(Pull Model) 

这种方式是在目标对象在状态发生变化时,仅仅是通知观察者事件已经发生了,而不会主动推送具体的状态数据。观察者在接收到通知后,需要主动调用目标对象提供的接口(如getState方法)来获取所需的状态数据。

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

// 观察者接口
interface Observer {
    // 当目标对象状态改变时,此方法会被调用,用于更新观察者状态
    void update();
}

// 目标对象接口
interface Subject {
    // 注册观察者
    void attach(Observer observer);
    // 移除观察者
    void detach(Observer observer);
    // 通知所有观察者状态发生了改变
    void notifyObservers();
    // 提供获取当前状态的方法,供观察者主动拉取
    String getState();
}

// 具体目标对象
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        // 遍历所有注册的观察者,调用其 update 方法通知状态改变
        for (Observer observer : observers) {
            observer.update();
        }
    }

    @Override
    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
        // 当状态设置完成后,通知所有观察者
        notifyObservers();
    }
}

// 具体观察者
class ConcreteObserver implements Observer {
    private String name;
    private Subject subject;

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

    @Override
    public void update() {
        // 主动从目标对象获取最新状态
        String newState = subject.getState();
        System.out.println(name + " 接收到新状态: " + newState);
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建具体目标对象
        ConcreteSubject subject = new ConcreteSubject();
        // 创建具体观察者对象,并关联目标对象
        ConcreteObserver observer1 = new ConcreteObserver("Observer1", subject);
        ConcreteObserver observer2 = new ConcreteObserver("Observer2", subject);

        // 注册观察者到目标对象
        subject.attach(observer1);
        subject.attach(observer2);

        // 改变目标对象的状态
        subject.setState("New State");
    }
}    

 拉模型的优点

  • 按需获取数据:观察者可以根据自身的实际需求,有选择性地去获取目标对象的状态数据。它可以只获取自己关心的那部分数据,避免了接收无关数据,提高了数据获取的针对性和效率。
  • 减少资源浪费:由于观察者只获取自己需要的数据,所以不会产生不必要的数据传输,节省了系统资源,尤其是在数据量较大的情况下优势更为明显。

拉模型的缺点

  • 增加交互复杂度:这种模型需要在目标对象和观察者之间明确定义数据获取的接口,并且观察者需要主动去调用这些接口获取数据。这增加了两者之间的交互逻辑和实现的复杂度。
  • 可能存在数据获取不及时:如果观察者没有及时响应目标对象的通知并调用接口获取数据,那么它获取到的数据可能就不是最新的状态。比如在一些对实时性要求极高的场景中,这种延迟可能会导致数据的不准确或不及时,影响系统的正常运行。

这里我们还是以天气信息系统为例区别于推模型,当天气数据采集中心检测到天气状态变化后,只通知各个用户终端有数据更新了。用户终端如果想要获取最新的天气信息,就需要主动调用采集中心提供的接口来获取。比如有些用户只关心风力信息,那么它就可以只获取风力相关的数据,而不用接收其他无关的天气数据。但如果用户终端没有及时去调用接口获取数据,就可能获取到的是旧的风力信息。

三、观察者模式在事件驱动的场景或框架应用

1. 跨模块状态同步的典型场景

在现代的微服务架构中,拿电商系统来说,订单模块和库存模块是两个紧密关联但又需要保持相对独立的核心模块 。在传统的紧密耦合设计中,当订单状态发生变化,如订单创建、支付成功、取消订单等操作时,订单模块需要直接调用库存模块的方法来更新库存数量,这就导致订单模块和库存模块之间的依赖关系非常紧密。如果库存模块的接口发生变化,订单模块也需要相应地修改代码,维护成本较高。我们来看一下传统紧密耦合设计:

// 库存模块类
class InventoryModule {
    // 更新库存数量的方法
    public void updateInventory(int quantity) {
        // 实际更新库存逻辑代码,比如连接数据库更新库存数量等
        System.out.println("库存数量更新为:" + quantity);
    }
}

// 订单模块类
class OrderModule {
    private InventoryModule inventoryModule;

    public OrderModule(InventoryModule inventoryModule) {
        this.inventoryModule = inventoryModule;
    }

    // 模拟订单支付成功操作
    public void orderPaid(int quantity) {
        // 订单支付成功,直接调用库存模块更新库存方法
        inventoryModule.updateInventory(quantity);
    }
}

这里OrderModule类中直接依赖InventoryModule类,orderPaid方法中直接调用InventoryModule的updateInventory方法,当InventoryModule接口变化时,OrderModule必须修改代码。 

引入观察者模式后,订单模块可以作为被观察对象(Subject),库存模块作为观察对象(Observer) 。当订单状态发生变化时,订单模块不再直接调用库存模块的方法,而是通知所有注册的观察者(包括库存模块)。库存模块接收到通知后,根据订单的最新状态进行相应的库存更新操作。

这样,订单模块和库存模块之间实现了解耦,双方可以独立地进行功能扩展和修改,互不影响。例如,当需要增加新的库存调整逻辑时,只需在库存模块中进行修改,订单模块无需感知;当订单模块进行业务逻辑优化时,也不会影响到库存模块的正常运行,极大地提高了系统的灵活性和可维护性 。 

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

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

// 抽象被观察对象类
abstract class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

// 订单模块类,作为被观察对象
class OrderModuleSubject extends Subject {
    // 模拟订单状态
    private String orderStatus;

    public String getOrderStatus() {
        return orderStatus;
    }

    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
        // 订单状态变化,通知观察者
        notifyObservers();
    }
}

// 库存模块类,作为观察者
class InventoryModuleObserver implements Observer {
    private OrderModuleSubject orderModule;

    public InventoryModuleObserver(OrderModuleSubject orderModule) {
        this.orderModule = orderModule;
        orderModule.attach(this);
    }

    @Override
    public void update() {
        // 根据订单状态进行库存更新操作
        String status = orderModule.getOrderStatus();
        if ("已支付".equals(status)) {
            // 实际更新库存逻辑代码,比如连接数据库更新库存数量等
            System.out.println("因为订单已支付,库存更新操作执行");
        }
    }
}

这样OrderModuleSubject作为被观察对象,InventoryModuleObserver作为观察者,OrderModuleSubject状态改变时通知InventoryModuleObserver,两者之间解耦,各自修改扩展不会相互影响。

2.Vue.js 框架中响应式原理与观察者模式​

Vue.js 是一款流行的前端 JavaScript 框架,其核心的响应式原理就运用了观察者模式。当数据发生变化时,Vue 会自动更新 DOM,这背后的机制就是观察者模式的体现。​

在 Vue 中,每个组件实例都对应一个Watcher实例,Watcher就是观察者。数据对象则是被观察对象。当数据对象的属性发生变化时,会通知所有依赖它的Watcher,Watcher再去更新对应的 DOM。​

下面是一个简单的 Vue 响应式原理模拟代码示例:

// 被观察对象
function data() {
    return {
        message: 'Hello, Vue!'
    }
}

// 观察者
function Watcher(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
}

Watcher.prototype = {
    get: function() {
        Dep.target = this;
        var value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    },
    update: function() {
        var oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm, this.value, oldValue);
    }
};

// 依赖收集器,管理观察者
function Dep() {
    this.subs = [];
}

Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

// 数据劫持
function defineReactive(obj, key, val) {
    var dep = new Dep();
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            if (Dep.target) {
                dep.addSub(Dep.target);
            }
            return val;
        },
        set: function(newVal) {
            if (newVal === val) return;
            val = newVal;
            dep.notify();
        }
    });
    return val;
}

// 测试
var vm = {};
var dataObj = data();
Object.keys(dataObj).forEach(function(key) {
    defineReactive(vm, key, dataObj[key]);
});

// 添加观察者
new Watcher(vm,'message', function(newVal, oldVal) {
    console.log('message changed from', oldVal, 'to', newVal);
});

// 修改数据触发更新
vm.message = 'New message';

defineReactive函数将数据对象的属性进行劫持,当属性被访问和修改时进行依赖收集和通知。Watcher实例作为观察者,在数据变化时会收到通知并执行回调函数。

3.Spring 框架内建事件机制解析 

Spring 框架的事件机制是基于观察者模式实现的,它提供了一种松耦合的方式来实现组件之间的通信和交互 。在 Spring 中,主要涉及三个核心概念:事件(Event)、事件监听器(EventListener)和事件发布者(EventPublisher)。​

事件是应用程序中发生的特定事情的抽象表示,它继承自ApplicationEvent类 。开发者可以根据业务需求自定义事件类,用于封装与事件相关的数据。我们可以定义OrderCreatedEvent类来表示订单创建事件,该类可以包含订单的详细信息,如订单编号、订单金额、下单时间等。​

import org.springframework.context.ApplicationEvent;

// 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;

    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder() {
        return order;
    }
}

事件监听器负责监听特定类型的事件,并在事件发生时执行相应的业务逻辑 。它实现了ApplicationListener接口,重写onApplicationEvent方法。在该方法中,监听器可以获取事件对象,并根据事件携带的数据进行处理。当OrderCreatedEvent事件发生时,订单处理监听器可以获取订单信息,进行库存检查、订单状态更新等操作。​

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class OrderCreatedListener implements ApplicationListener<OrderCreatedEvent> {

    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // 处理订单创建后的逻辑,如库存检查等
        System.out.println("Processing order: " + order.getOrderNumber());
    }
}

事件发布者负责发布事件,它通过注入ApplicationEventPublisher对象,调用publishEvent方法来发布事件 。当某个业务逻辑执行完成后,如订单创建成功,事件发布者可以发布相应的事件,通知所有注册的监听器。

在订单服务中,当创建订单的方法执行成功后,调用applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, order))来发布订单创建事件,其中this表示事件源,order是订单对象 。通过这种方式,Spring 框架的事件机制实现了组件之间的解耦,提高了系统的可扩展性和维护性。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void createOrder(Order order) {
        // 创建订单逻辑
        System.out.println("Order created: " + order.getOrderNumber());

        // 发布订单创建事件
        applicationEventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

四、观察者模式的简单业务实现​:音乐播放系统状态变更通知场景构建

在音乐播放系统中,播放状态的变更需要及时通知到多个相关模块,如歌词显示模块、歌曲进度模块、用户通知模块等。以一个简单的音乐播放流程为例,当用户点击播放按钮后,播放状态从 “暂停” 变为 “播放中”,此时需要通知歌词显示模块开始滚动歌词,通知歌曲进度模块实时更新进度,同时给用户发送播放提示消息。随着歌曲的暂停、切换、结束等状态的不断变化,都需要及时准确地通知到各个相关模块,以保证用户体验的流畅性。​

在这个场景中,音乐播放对象充当被观察对象(Subject),歌词显示模块、歌曲进度模块、用户通知模块等则是观察对象(Observer)。当播放状态发生变化时,音乐播放对象会通知所有注册的观察对象,各个观察对象根据自身的业务逻辑进行相应的处理。

这种情况下我们可以基于 ApplicationEventPublisher 的观察者实现上述需求,在 Spring Boot 中,可以利用ApplicationEventPublisher来实现播放状态变更通知。首先,定义播放状态变更事件类:

import org.springframework.context.ApplicationEvent;

public class PlayStatusChangeEvent extends ApplicationEvent {

    private String songId;
    private String newStatus;

    public PlayStatusChangeEvent(Object source, String songId, String newStatus) {
        super(source);
        this.songId = songId;
        this.newStatus = newStatus;
    }

    public String getSongId() {
        return songId;
    }

    public String getNewStatus() {
        return newStatus;
    }
}

在这个事件类中,PlayStatusChangeEvent继承自ApplicationEvent,通过构造函数传入事件源、歌曲 ID 和新的播放状态。​然后,我们在音乐播放服务中发布事件:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class MusicPlayService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void updatePlayStatus(String songId, String newStatus) {
        // 业务逻辑处理,如更新播放状态到数据库
        // ...

        // 发布播放状态变更事件
        applicationEventPublisher.publishEvent(new PlayStatusChangeEvent(this, songId, newStatus));
    }
}

在MusicPlayService中,通过依赖注入获取ApplicationEventPublisher实例,在updatePlayStatus方法中,当播放状态更新完成后,调用publishEvent方法发布PlayStatusChangeEvent事件。​接着,定义事件监听器来处理事件:

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class LyricsDisplayListener implements ApplicationListener<PlayStatusChangeEvent> {

    @Override
    public void onApplicationEvent(PlayStatusChangeEvent event) {
        if ("播放中".equals(event.getNewStatus())) {
            // 滚动歌词逻辑
            System.out.println("歌词显示模块收到歌曲 " + event.getSongId() + " 已播放通知,开始滚动歌词操作");
        }
    }
}

@Component
public class SongProgressListener implements ApplicationListener<PlayStatusChangeEvent> {

    @Override
    public void onApplicationEvent(PlayStatusChangeEvent event) {
        if ("播放中".equals(event.getNewStatus())) {
            // 更新进度逻辑
            System.out.println("歌曲进度模块收到歌曲 " + event.getSongId() + " 已播放通知,实时更新进度操作");
        }
    }
}

@Component
public class UserNotificationListener implements ApplicationListener<PlayStatusChangeEvent> {

    @Override
    public void onApplicationEvent(PlayStatusChangeEvent event) {
        // 发送通知逻辑
        System.out.println("用户通知模块收到歌曲 " + event.getSongId() + " 状态变更为 " + event.getNewStatus() + " 的通知,发送通知操作");
    }
}

这里我们分别定义了LyricsDisplayListener、SongProgressListener和UserNotificationListener三个事件监听器,它们实现了ApplicationListener接口,重写onApplicationEvent方法,在方法中根据不同的播放状态进行相应的业务处理。

但是在实际业务中,播放状态变更通知的处理可能涉及一些耗时操作,如从网络加载歌词、调用第三方音乐接口等。如果这些操作在主线程中同步执行,会导致播放状态更新的响应时间变长,影响用户体验。因此,引入异步事件处理机制是很有必要的。​

在 Spring Boot 中,我们可以使用@Async注解来实现异步事件处理。首先,在 Spring Boot 启动类上添加@EnableAsync注解,开启异步功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后,在事件监听器的处理方法上添加@Async注解:

import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class UserNotificationListener implements ApplicationListener<PlayStatusChangeEvent> {

    @Override
    @Async
    public void onApplicationEvent(PlayStatusChangeEvent event) {
        // 发送通知逻辑,如发送推送消息
        System.out.println("用户通知模块收到歌曲 " + event.getSongId() + " 状态变更为 " + event.getNewStatus() + " 的通知,发送通知操作");
        // 模拟发送消息耗时操作
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在UserNotificationListener的onApplicationEvent方法上添加@Async注解后,当该方法被调用时,Spring 会将其放入一个线程池中异步执行,不会阻塞主线程。这样,播放状态更新的操作可以快速返回,提高了系统的响应性能。同时,通过合理配置线程池参数,可以控制异步任务的并发执行数量,避免资源耗尽等问题。

五、总结

观察者模式的本质是定义了对象之间一种动态的通讯契约,它构建起了被观察对象与观察对象之间松耦合的交互桥梁。通过这种契约关系,被观察对象专注于自身状态的维护和变化,当状态发生改变时,依据契约自动触发通知机制,将变化告知所有与之关联的观察对象 。而观察对象则依据契约中定义的更新方法,对收到的通知做出相应的反应,执行自身的业务逻辑。这种动态通讯契约使得系统中的各个对象能够在保持相对独立的同时,实现高效的信息交互和协同工作,极大地提高了系统的灵活性和可扩展性 。

其实说了这么多,大家肯定会发现,这个模式很像发布订阅模式。这里我们就不得不说一下他们之间的对比区别。虽然观察者模式和发布订阅模式在很多方面具有相似性,都涉及到对象之间的消息传递和依赖关系管理,但它们之间也存在一些明显的区别边界:​

定义和概念:观察者模式主要定义了对象间一种直接的一对多依赖关系,被观察对象(Subject)直接与观察对象(Observer)进行交互 。

而发布订阅模式中,发布者(Publisher)和订阅者(Subscriber)之间通过一个中间的事件调度中心(Event Channel)进行通信,双方并不直接交互 。​

结构和依赖关系:在观察者模式中,被观察对象需要维护一个观察者列表,并且需要了解观察者的具体接口,以便在状态变化时能够直接调用观察者的更新方法,因此被观察对象与观察者之间存在一定的耦合度 。

而在发布订阅模式中,发布者和订阅者都只与事件调度中心交互,它们之间没有直接的依赖关系,耦合度更低,实现了更彻底的解耦 。​

消息通知方式:观察者模式中,当被观察对象状态发生变化时,会主动调用所有注册观察者的更新方法,将变化通知给观察者 。

而发布订阅模式中,发布者将事件发布到事件调度中心后,由事件调度中心负责将事件推送给所有订阅了该事件的订阅者,订阅者并不知道事件是由哪个发布者发布的 。​

应用场景:观察者模式更适用于在一个系统内部,模块之间存在明确的依赖关系,并且需要及时进行状态同步和消息通知的场景 。

发布订阅模式则更适用于在分布式系统、跨系统通信或者需要实现更灵活的事件处理机制的场景 。例如,在一个微服务架构中,不同的服务之间通过消息队列进行事件的发布和订阅,实现服务之间的解耦和异步通信 。

另外在我们使用观察者模式时,一定要注意循环引用和内存泄漏的问题 。

循环引用通常发生在被观察对象和观察对象之间相互持有对方的引用 。例如,被观察对象在其观察者列表中持有观察对象的引用,而观察对象在其内部状态中又持有被观察对象的引用。这样,当程序试图释放其中一个对象时,由于对方持有自己的引用,导致垃圾回收器无法回收这些对象,从而造成内存泄漏 。​

为了避免循环引用内存泄漏,大家可以采取以下措施:​

  • 及时移除观察者:在观察对象不再需要接收被观察对象的通知时,要及时调用被观察对象的移除方法,将观察对象从观察者列表中移除 。例如,在一个 Android 应用中,Activity 作为观察对象注册到某个数据模型(被观察对象)上,当 Activity 销毁时,要确保调用数据模型的移除方法,将 Activity 从观察者列表中移除,避免内存泄漏 。​
  • 使用弱引用:可以使用弱引用(WeakReference)来持有观察者对象 。在 Java 中,可以通过WeakReference类来实现。使用弱引用后,即使被观察对象仍然持有观察者的弱引用,当观察者对象没有其他强引用指向它时,垃圾回收器仍然可以回收观察者对象,从而避免内存泄漏。​
  • 合理设计对象生命周期:在设计系统时,要合理规划被观察对象和观察对象的生命周期,确保它们的生命周期相互匹配 。例如,避免创建生命周期过长的被观察对象,而观察对象的生命周期却很短,导致观察对象长时间无法被回收 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

深情不及里子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值