You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用事件结合Observer Pattern实现新闻订阅者的增删与推送?

当然可以用事件来实现观察者模式的新闻通讯订阅功能!事件驱动本身就是观察者模式的经典落地方式,完全匹配你要做的「添加/移除订阅者+推送消息」需求。我结合你提到的控制台表单场景,给你拆解下具体思路和代码实现:

核心逻辑对应关系

先把观察者模式的角色和事件系统对应起来,方便你理解:

  • 发布者(Subject):就是你的新闻通讯服务,负责管理订阅者、触发推送事件
  • 订阅者(Observer):每个订阅者就是一个事件监听器,通过绑定事件来接收消息
  • 添加订阅:给发布者绑定一个自定义事件的监听器
  • 移除订阅:解绑对应的事件监听器
  • 推送新闻:触发发布者的自定义事件,把新闻内容传递给所有监听器
具体实现步骤(以JavaScript为例)

1. 实现新闻发布者类

我们可以利用浏览器原生的EventTarget来实现发布者,它自带事件绑定、解绑和触发的能力,不用自己手动写循环通知逻辑:

class NewsletterPublisher extends EventTarget {
  constructor() {
    super();
    // 用Map存订阅者ID和对应的回调,方便精准移除
    this.subscribers = new Map();
  }

  // 添加订阅者:绑定事件+记录回调
  addSubscriber(subscriberId, callback) {
    if (!this.subscribers.has(subscriberId)) {
      this.addEventListener('newsletter', callback);
      this.subscribers.set(subscriberId, callback);
      console.log(`✅ 订阅者 ${subscriberId} 已添加`);
    } else {
      console.log(`⚠️ 订阅者 ${subscriberId} 已经存在`);
    }
  }

  // 移除订阅者:解绑事件+删除记录
  removeSubscriber(subscriberId) {
    const callback = this.subscribers.get(subscriberId);
    if (callback) {
      this.removeEventListener('newsletter', callback);
      this.subscribers.delete(subscriberId);
      console.log(`❌ 订阅者 ${subscriberId} 已移除`);
    } else {
      console.log(`⚠️ 订阅者 ${subscriberId} 不存在`);
    }
  }

  // 推送新闻:触发自定义事件,传递新闻内容
  publishNewsletter(newsContent) {
    const newsEvent = new CustomEvent('newsletter', {
      detail: newsContent // 把新闻内容存在事件的detail属性里
    });
    this.dispatchEvent(newsEvent);
    console.log(`📢 新闻通讯已推送:${newsContent}`);
  }
}

2. 绑定表单按钮的交互逻辑

假设你的HTML表单有这些元素(输入订阅者ID、新闻内容,三个操作按钮),我们给按钮绑定点击事件,调用发布者的方法:

<!-- 示例HTML结构 -->
<div class="newsletter-form">
  <input type="text" id="subscriber-id" placeholder="输入订阅者ID">
  <button id="add-btn">添加订阅者</button>
  <button id="remove-btn">移除订阅者</button>
  <br>
  <textarea id="news-content" placeholder="输入新闻内容"></textarea>
  <button id="publish-btn">推送新闻</button>
</div>

对应的JavaScript交互代码:

// 初始化发布者实例
const publisher = new NewsletterPublisher();

// 获取DOM元素
const addBtn = document.getElementById('add-btn');
const removeBtn = document.getElementById('remove-btn');
const publishBtn = document.getElementById('publish-btn');
const subscriberIdInput = document.getElementById('subscriber-id');
const newsContentInput = document.getElementById('news-content');

// 添加订阅者按钮逻辑
addBtn.addEventListener('click', () => {
  const id = subscriberIdInput.value.trim();
  if (id) {
    // 每个订阅者的回调可以自定义,这里简单打印收到的新闻
    const onReceiveNews = (event) => {
      console.log(`📩 订阅者 ${id} 收到新闻:${event.detail}`);
    };
    publisher.addSubscriber(id, onReceiveNews);
    subscriberIdInput.value = ''; // 清空输入框
  }
});

// 移除订阅者按钮逻辑
removeBtn.addEventListener('click', () => {
  const id = subscriberIdInput.value.trim();
  if (id) {
    publisher.removeSubscriber(id);
    subscriberIdInput.value = '';
  }
});

// 推送新闻按钮逻辑
publishBtn.addEventListener('click', () => {
  const content = newsContentInput.value.trim();
  if (content) {
    publisher.publishNewsletter(content);
    newsContentInput.value = '';
  }
});
关键细节说明
  • 为什么用EventTarget?:它是浏览器原生的事件目标对象,自带addEventListener、removeEventListener和dispatchEvent方法,帮我们省了手动维护监听器列表和循环通知的代码,减少bug。
  • 为什么用Map存订阅者?:因为removeEventListener需要传入和添加时完全相同的回调函数,用Map把订阅者ID和回调绑定起来,就能精准找到要解绑的函数。
  • 自定义事件的detail属性:这是传递数据的标准方式,订阅者可以通过事件对象的detail拿到推送的新闻内容。
扩展优化建议
  • 可以在页面上添加一个「当前订阅者列表」的展示区域,每次添加/移除后更新列表,更直观。
  • 如果订阅者是复杂对象,可以让对象实现一个update方法,把这个方法作为回调绑定到事件上,更符合观察者模式的面向对象写法。
  • 可以给输入框添加校验逻辑,比如订阅者ID不能为空、不能包含特殊字符等。

内容的提问来源于stack exchange,提问作者Cleaven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:41:02