观察者模式与发布订阅模式:解耦与通信的艺术

个人博客:haichenyi.com。感谢关注

一. 目录

  • 一–目录
  • 二–引言
  • 三–观察者模式:直接依赖的同步通信​​
  • 四–发布订阅模式:解耦的异步通信​
  • 五–核心区别与对比​​
  • 六–实际应用场景​​​
  • 七–如何选择?​​​
  • 八–总结​​​​

二. 引言

  在软件开发中,组件间的通信是核心挑战之一。如何让对象在状态变化时通知其他对象,同时保持代码的灵活性和可维护性?观察者模式(Observer Pattern)和发布订阅模式(Publish-Subscribe Pattern)为此提供了经典解决方案。尽管两者目标相似(实现松耦合通信),但其设计哲学和应用场景却截然不同。本文将通过理论解析、代码示例和实际应用场景,深入探讨它们的区别与选择策略。
  举个案例,登录监听,有三个页面需要监听页面的变化,登录成功之后,通知页面更新

interface LoginObserver {
    loginUpdate(isLogin: boolean)
}

class PageALoginOberver implements LoginObserver {
    loginUpdate(isLogin: boolean) {
        console.log("PageALoginOberver");
    }
}

class PageBLoginOberver implements LoginObserver {
    loginUpdate(isLogin: boolean) {
        console.log("PageBLoginOberver");
    }
}

class PageCLoginOberver implements LoginObserver {
    loginUpdate(isLogin: boolean) {
        console.log("PageCLoginOberver");
    }
}

class LoginSubject {
    observerList: LoginObserver[]
    constructor() {
        this.observerList = []
    }
    addObserver(observer: LoginObserver) {
        if (!observer || this.observerList.indexOf(observer) !== -1) {
            return
        }
        this.observerList.push(observer)
    }
    removeObserver(observer: LoginObserver) {
        if (!observer) {
            return
        }
        let index = this.observerList.indexOf(observer)
        if (index !== -1) {
            this.observerList.splice(index, 1);
        }
    }
    notify(isLogin: boolean) {
        this.observerList.forEach(item => item.loginUpdate(isLogin))
    }
}

let loginSubject = new LoginSubject()

let pageALoginOberver = new PageALoginOberver()
let pageBLoginOberver = new PageBLoginOberver()
let pageCLoginOberver = new PageCLoginOberver()
loginSubject.addObserver(pageALoginOberver);
loginSubject.addObserver(pageBLoginOberver);
loginSubject.addObserver(pageCLoginOberver);

//在登录成功的位置调用
loginSubject.notify(true)

这个案例属于哪种模式呢?

三. 观察者模式:直接依赖的同步通信

1. 核心思想​
  观察者模式定义了一种一对多的依赖关系: 当一个对象(被观察者Subject)的状态发生变化时,所有依赖它的对象(观察者Observer)会自动收到通知并更新
角色定义
- Subject(被观察者): ​维护观察者列表,提供注册、注销和通知方法。
- Observer(观察者)​​: 定义接收通知的接口(如 update() 方法)。
2. 实现示例

//上面引言中提到的案例,就是典型的观察者模式
//这里再举一个其他的案例,Android里面用的比较多的,Activity的生命周期的监听
// Subject(被观察者)
public class ActivityLifecycleManager {
    private List listeners = new ArrayList<>();

    public void addListener(LifecycleListener listener) {
        listeners.add(listener);
    }

    public void removeListener(LifecycleListener listener) {
        listeners.remove(listener);
    }

    private void notifyListeners(String event) {
        for (LifecycleListener listener : listeners) {
            listener.onEvent(event); // 直接调用 Observer 的方法
        }
    }

    // 在 Activity 生命周期回调中触发通知
    public void onPause() {
        notifyListeners("onPause");
    }
}

// Observer(观察者)
public interface LifecycleListener {
    void onEvent(String event);
}

// 使用示例
ActivityLifecycleManager manager = new ActivityLifecycleManager();
manager.addListener(new LifecycleListener() {
    @Override
    public void onEvent(String event) {
        Log.d("TAG", "收到事件: " + event);
    }
});
//在 Activity 生命周期回调中触发通知
manager.onPause()

3. 特点

  • 强耦合​​:Subject 直接持有 Observer 的引用。
  • 同步通信​​:通知是即时且阻塞的(如直接调用 Observer 的方法)。
  • 适用场景​​:对象间直接依赖的简单通信。(如数据模型更新)

四. 发布订阅模式:解耦的异步通信

1. 核心思想
  发布订阅模式通过引入事件通道(Event Channel) 解耦发布者(publish)订阅者(Subscriber) 。发布者无需知道谁订阅了事件,订阅者也无需知道事件来源,二者仅通过事件类型标识符来通信。
角色定义
- Publisher(发布者)​​: 触发事件,向通道发送消息。
- Subscriber(订阅者)​​: 向通道注册回调,监听特定事件。
- Event Channel(事件通道)​​: ​管理事件路由,负责消息传递。
2. 实现示例

//上面的activity生命周期的监听,改成发布订阅,该怎么写呢?
// 事件总线(核心发布订阅逻辑)
//定义事件通道(Event Bus)​
public class EventBus {
    private static EventBus instance;
    private Map> eventListeners = new HashMap<>();

    // 单例模式
    public static synchronized EventBus getInstance() {
        if (instance == null) {
            instance = new EventBus();
        }
        return instance;
    }

    // 订阅事件
    public void subscribe(String eventType, EventListener listener) {
        List listeners = eventListeners.get(eventType);
        if (listeners == null) {
            listeners = new ArrayList<>();
            eventListeners.put(eventType, listeners);
        }
        listeners.add(listener);
    }

    // 取消订阅
    public void unsubscribe(String eventType, EventListener listener) {
        List listeners = eventListeners.get(eventType);
        if (listeners != null) {
            listeners.remove(listener);
        }
    }

    // 发布事件
    public void publish(String eventType, String eventData) {
        List listeners = eventListeners.get(eventType);
        if (listeners != null) {
            for (EventListener listener : listeners) {
                listener.onEvent(eventData);
            }
        }
    }

    // 事件监听接口
    public interface EventListener {
        void onEvent(String eventData);
    }
}
//发布者(Activity 生命周期触发事件)​
//在 Activity 生命周期回调中发布事件,而不是直接调用监听器。
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 发布 onCreate 事件
        EventBus.getInstance().publish("activity_create", "MainActivity created");
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 发布 onPause 事件
        EventBus.getInstance().publish("activity_pause", "MainActivity paused");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 发布 onDestroy 事件
        EventBus.getInstance().publish("activity_destroy", "MainActivity destroyed");
    }
}
//订阅者(监听生命周期事件)
//在任何地方订阅感兴趣的事件类型(如 activity_pause)。
public class MySubscriber implements EventBus.EventListener {
    public MySubscriber() {
        // 订阅 activity_pause 事件
        EventBus.getInstance().subscribe("activity_pause", this);
    }

    @Override
    public void onEvent(String eventData) {
        Log.d("PubSub", "收到事件: " + eventData);
        // 处理事件逻辑(如保存数据、更新 UI)
    }

    // 取消订阅(避免内存泄漏)
    public void dispose() {
        EventBus.getInstance().unsubscribe("activity_pause", this);
    }
}

// 使用示例
MySubscriber subscriber = new MySubscriber();

  有大聪明就会说了,我原来几行代码就写完了,你现在搞这么多代码,这么复杂。的确,上面activity生命周期,改成发布订阅者模式代码确实变多了。任何,设计的选型,都是需要结合实际情况和考虑后续拓展的。比方说,这里的生命周期的监听,你如果只监听了一种生命周期,那的确用观察者模式会简单很多。但是,activity的生命周期可不止一个啊。要监听多个生命后期,你如果用观察者模式,你需要加方法,加监听等等之类的。但是,你如果用发布订阅者模式,你啥都不用改,你只用改事件类型就可以了。这也是发布订阅者的其中一个优点:支持多对多通信,动态添加事件类型。
3. 特点

  • 完全解耦​​: 发布者和订阅者通过事件通道间接通信。
  • 灵活扩展​​: 支持多对多通信,动态添加事件类型。
  • 适用场景​​: 跨组件通信、微服务架构、分布式系统(如 Kafka、RabbitMQ)。

  有人就是表示不服,说,他们实际上触发就是list的foreach循环,触发回调。只不过观察者模式是直接触发,发布订阅者是间接触发。欸,对,你说的没错。这就是区别。一个是直接触发,一个是间接触发。判断一个代码的好与坏,就一个标准。高类聚,低耦合。 观察者模式的直接触发,就是被观察者直接获取到观察者的引用,这就是高耦合。后面修改就比较麻烦,改动一点点,影响比较大。发布订阅者,这方面就比观察者好。

  不要杠,杠那就是你说的都对。

五. 核心区别与对比

维度 观察者模式 发布订阅模式
耦合性​ 强耦合(Subject 直接管理 Observer) 低耦合(通过事件通道解耦)
​​通信方式​​ 同步调用(如直接方法调用) 可同步或异步(支持消息队列)
事件类型支持​​ 需手动扩展接口或参数区分 天然支持多事件类型
性能与复杂度​​ 轻量级,适合简单场景 需事件通道,适合复杂系统
典型应用​​ 数据绑定 全局事件总线、分布式消息系统

六. 实际应用场景

  1. 观察者模式的应用​
  • 前端框架的响应式系统
    Vue 2 通过 Object.defineProperty 劫持数据属性的 getter/setter,结合 Dep 和 Watcher 实现数据变化到视图的同步。
// Vue 2 响应式原理简化实现
class Dep {
  constructor() { this.subs = []; }
  depend() { this.subs.push(Dep.target); }
  notify() { this.subs.forEach(watcher => watcher.update()); }
}

function defineReactive(obj, key, val) {
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) dep.depend();
      return val;
    },
    set(newVal) {
      val = newVal;
      dep.notify();
    }
  });
}
  • Android 生命周期监听​
    Activity 直接管理生命周期监听器列表,状态变化时遍历触发回调。
  1. 发布订阅模式的应用​
  • ​Vue 的全局事件总线
// 注册全局事件总线
Vue.prototype.$eventBus = new Vue();

// 组件A:发布事件
this.$eventBus.$emit('data-updated', { data: 123 });

// 组件B:订阅事件
this.$eventBus.$on('data-updated', (data) => {
  console.log('收到数据:', data);
});
  • Android里面的EventBus库
  • 微服务架构中的消息中间件​
    使用 Kafka 或 RabbitMQ 实现服务间的异步通信,确保系统可扩展性和容错性。

七. 如何选择?

  1. 选择观察者模式的条件​
    • 对象间存在直接依赖关系。
    • 事件类型较少,且不需要动态扩展。
    • 需要简单、轻量级的同步通信。
  2. 选择发布订阅模式的条件​
    • 需要跨组件或跨服务通信。
    • 事件类型多样,且可能动态增减。
    • 系统需要解耦以提升可维护性和扩展性。

八. 总结​

观察者模式与发布订阅模式代表了两种不同的解耦哲学:

  • ​​观察者模式​​ 是 “我直接通知你”,强调直接、高效的同步通信。
  • 发布订阅模式​​ 是 “我发到频道,谁爱听谁听”,强调灵活性和解耦。

这又让我想到了,类似于TCP与UDP通信的区别。就这样吧,讲完了。实践是检验真理的唯一标准。光看可能云里雾里的,实际场景中用到了,就知道了。

两者在代码实现上有相似之处(遍历列表触发回调)。​​但它们的核心区别不在于数据结构(List 或 Map),而在于设计思想和应用场景​​。

你可能感兴趣的:(Java设计模式,观察者模式,发布订阅模式)