观察者模式 logo

观察者模式

Organization
microwind
观察者模式

定义对象间一对多的依赖关系,当对象状态改变时通知所有依赖者。在设计事件系统、MVC框架、响应式编程等场景中使用。

Overview

Publishermicrowind
Repositoryai-skills
Skill name观察者模式
Stars
93
Forks
25
Bundled files
3
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 3 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by microwind on GitHub. Read the source before you install it.

Installation

Install the 观察者模式 AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/microwind/ai-skills/tree/main/design-patterns/observer-pattern
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/microwind/ai-skills.git /tmp/ai-skills
mkdir -p .claude/skills
cp -r /tmp/ai-skills/design-patterns/observer-pattern .claude/skills/microwind-29
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable 观察者模式 in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads 观察者模式 on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so 观察者模式 is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

观察者模式 (Observer Pattern)

概述

观察者模式定义了对象间的一对多依赖关系。当一个对象(称为Subject)的状态改变时,它的所有依赖者(称为Observers)都会自动收到通知并更新。

核心原则:

  • 🎯 一对多订阅:一个发布者,多个订阅者
  • 📢 自动通知:状态改变时主动通知所有观察者
  • 🔌 松耦合:发布者和订阅者相互独立
  • ⚙️ 动态关系:运行时动态添加/移除观察者

何时使用

完美适用场景

场景说明优先级
事件处理系统GUI 按钮点击、文本变更等事件⭐⭐⭐
MVC 架构Model 变更→View 自动更新⭐⭐⭐
响应式编程数据绑定、实时流处理⭐⭐⭐
消息系统消息队列、事件总线⭐⭐⭐
属性变更通知对象属性变化通知⭐⭐
模型-视图绑定Web 框架中的数据绑定⭐⭐⭐

触发信号(何时考虑使用)

✅ 以下信号表明应该使用观察者:

  • "状态改变时需要通知多个对象"
  • "对象间存在一对多的依赖关系"
  • "需要松耦合的事件通知机制"
  • "需要动态添加/移除监听器"
  • "事件驱动的应用架构"

❌ 以下情况不应该使用:

  • "只有单向的命令式调用"
  • "通知链太长导致性能问题"
  • "观察者间存在复杂的依赖"

观察者模式的优缺点

优点 ✅

  1. 高度解耦

    • 发布者和订阅者完全解耦
    • 可以独立修改
    • 易于单元测试
  2. 动态关系

    • 运行时动态添加/移除观察者
    • 支持一对多的灵活关系
    • 无需修改发布者代码
  3. 自动通知

    • 状态改变时自动通知
    • 不需要主动查询
    • 反应式编程的基础
  4. 支持广播通信

    • 一个消息发送给多个接收者
    • 简化事件处理逻辑
    • 易于实现事件总线

缺点 ❌

  1. 无法控制通知顺序

    • 多个观察者的通知顺序不确定
    • 可能导致竞态条件
    • 依赖关系复杂时难以调试
  2. 内存泄漏风险

    • ❌ 观察者未正确反注册会持有引用
    • ❌ 导致内存泄漏
    • ❌ 特别是在 Web/移动应用中
  3. 性能问题

    • 观察者过多时性能下降
    • 同步通知会阻塞发布者
    • 异步通知增加复杂性
  4. 调试困难

    • 控制流不明显
    • 难以追踪执行路径
    • 出现问题难以定位

观察者模式的 3 种实现方式

1️⃣ 推模式(Push)- 发布者主动推送数据 ⭐⭐⭐

发布者将状态数据直接传给观察者。

java
/**
 * 推模式 - 发布者将状态数据推送给观察者
 * 优点: 观察者获取完整信息,无需查询
 * 缺点: 观察者必须接受所有发送的数据
 */

// 事件数据对象
public class StockEvent {
    private String stockCode;
    private double price;
    private double change;
    
    public StockEvent(String code, double price, double change) {
        this.stockCode = code;
        this.price = price;
        this.change = change;
    }
    
    // Getters...
}

// 观察者接口
public interface StockObserver {
    void onStockUpdate(StockEvent event);
}

// 具体观察者
public class InvestorObserver implements StockObserver {
    private String name;
    
    public InvestorObserver(String name) {
        this.name = name;
    }
    
    @Override
    public void onStockUpdate(StockEvent event) {
        System.out.printf("%s: 股票 %s 价格 %.2f, 涨幅 %.2f%%\n",
            name, event.getStockCode(), event.getPrice(), event.getChange());
    }
}

// 发布者
public class StockMarket {
    private List<StockObserver> observers = new ArrayList<>();
    
    public void attach(StockObserver observer) {
        observers.add(observer);
    }
    
    public void detach(StockObserver observer) {
        observers.remove(observer);
    }
    
    public void notifyObservers(String code, double price, double change) {
        StockEvent event = new StockEvent(code, price, change);
        for (StockObserver observer : observers) {
            observer.onStockUpdate(event);
        }
    }
}

// 使用
StockMarket market = new StockMarket();
StockObserver investor1 = new InvestorObserver("张三");
StockObserver investor2 = new InvestorObserver("李四");

market.attach(investor1);
market.attach(investor2);

market.notifyObservers("AAPL", 150.5, 2.5);  // 通知所有观察者

特点:

  • ✅ 轻松获得完整数据
  • ✅ 实现简单
  • ❌ 发送不关心的数据浪费资源
  • 🎯 适合: 数据更新需要完整信息的场景

2️⃣ 拉模式(Pull)- 观察者主动查询状态 ⭐⭐

发布者只通知观察者有更新,具体数据由观察者主动查询。

java
/**
 * 拉模式 - 发布者仅通知更新,观察者主动查询
 * 优点: 观察者只获取需要的信息
 * 缺点: 观察者需要主动查询,增加复杂度
 */

public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
    
    // 观察者查询的方法
    String getState();
}

public interface Observer {
    void update(Subject subject);  // 仅通知,不传数据
}

public class ConcreteSubject implements Subject {
    private String state;
    private List<Observer> observers = new ArrayList<>();
    
    @Override
    public void attach(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
        }
    }
    
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);  // 只通知,传递 Subject 引用
        }
    }
    
    @Override
    public String getState() {
        return state;
    }
    
    public void setState(String newState) {
        this.state = newState;
        notifyObservers();
    }
}

public class ConcreteObserver implements Observer {
    private String name;
    
    public ConcreteObserver(String name) {
        this.name = name;
    }
    
    @Override
    public void update(Subject subject) {
        // 主动查询状态
        System.out.println(name + "观察到状态变更: " + subject.getState());
    }
}

特点:

  • ✅ 观察者按需获取信息
  • ✅ 减少数据传输
  • ❌ 需要多次查询,性能一般
  • 🎯 适合: 少量观察者关心少量数据的场景

3️⃣ 事件总线(Event Bus)- 中介者模式结合 ⭐⭐⭐

使用中央事件总线,解耦发布者和订阅者完全。

java
/**
 * 事件总线模式 - 最灵活且最常用
 * 优点: 完全解耦发布者和订阅者,易于扩展
 * 缺点: 多了一个中介,稍微增加复杂度
 */

// 事件接口
public interface Event {}

// 具体事件
public class UserLoginEvent implements Event {
    public final String userId;
    public final String timestamp;
    
    public UserLoginEvent(String userId, String timestamp) {
        this.userId = userId;
        this.timestamp = timestamp;
    }
}

public class UserLogoutEvent implements Event {
    public final String userId;
    
    public UserLogoutEvent(String userId) {
        this.userId = userId;
    }
}

// 事件监听器
public interface EventListener {
    void onEvent(Event event);
}

// 事件总线
public class EventBus {
    private final Map<Class<?>, List<EventListener>> listeners = new ConcurrentHashMap<>();
    
    @SuppressWarnings("unchecked")
    public <T extends Event> void subscribe(Class<T> eventType, EventListener listener) {
        listeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
                 .add(listener);
    }
    
    public <T extends Event> void unsubscribe(Class<T> eventType, EventListener listener) {
        List<EventListener> eventListeners = listeners.get(eventType);
        if (eventListeners != null) {
            eventListeners.remove(listener);
        }
    }
    
    public void publish(Event event) {
        List<EventListener> eventListeners = listeners.get(event.getClass());
        if (eventListeners != null) {
            for (EventListener listener : eventListeners) {
                listener.onEvent(event);
            }
        }
    }
}

// 使用
EventBus bus = new EventBus();

bus.subscribe(UserLoginEvent.class, event -> {
    System.out.println("用户 " + event.userId + " 登录");
});

bus.subscribe(UserLogoutEvent.class, event -> {
    System.out.println("用户 " + event.userId + " 登出");
});

// 发布事件
bus.publish(new UserLoginEvent("user123", "2024-03-29"));
bus.publish(new UserLogoutEvent("user123"));

特点:

  • ✅ 完全解耦
  • ✅ 高度灵活可扩展
  • ✅ 现代化设计
  • ✅ 支持多种事件类型
  • 🎯 最推荐的实现方式

常见问题与解决方案

问题 1: 内存泄漏 - 观察者未反注册

现象: 应用内存不断增长,最终 OutOfMemory

原因: 观察者被添加后未被移除,发布者持有对观察者的强引用

解决方案:

java
// ❌ 错误做法 - 强引用导致内存泄漏
public class View {
    private EventBus bus = new EventBus();
    
    public View() {
        bus.subscribe(UserEvent.class, (event) -> {
            update();
        });
        // 没有注销监听器,View 销毁时内存被占用
    }
}

// ✅ 正确做法1 - 显式注销
public class View implements Closeable {
    private EventBus bus;
    private EventListener listener;
    
    public View(EventBus bus) {
        this.bus = bus;
        this.listener = (event) -> update();
        bus.subscribe(UserEvent.class, listener);
    }
    
    @Override
    public void close() {
        bus.unsubscribe(UserEvent.class, listener);
    }
}

// ✅ 正确做法2 - 弱引用观察者
public class WeakObserverHolder<T extends Event> {
    private WeakReference<EventListener> listenerRef;
    private Class<T> eventType;
    
    public WeakObserverHolder(EventListener listener, Class<T> eventType) {
        this.listenerRef = new WeakReference<>(listener);
        this.eventType = eventType;
    }
    
    public void onEvent(Event event) {
        EventListener listener = listenerRef.get();
        if (listener != null) {
            listener.onEvent(event);
        }
    }
}

// ✅ 正确做法3 - 使用 try-with-resources
try (EventSubscription sub = bus.subscribe(UserEvent.class, listener)) {
    // 事件处理
}  // 自动注销

最佳实践:

  • 使用 try-with-resources 或显式 close() 方法
  • 在 Activity/Fragment/Component 销毁时注销
  • 考虑使用弱引用观察者

问题 2: 通知顺序不确定

现象: 多个观察者的通知顺序随意,导致依赖的业务逻辑出错

解决方案:

java
// ❌ 错误做法 - 顺序不确定
eventBus.publish(new StateChangeEvent());  // 多个监听器顺序不确定

// ✅ 正确做法1 - 指定优先级
public class PriorityEventBus {
    private static class ListenerWrapper {
        int priority;
        EventListener listener;
    }
    
    public void subscribe(Class<?> eventType, EventListener listener, int priority) {
        // 按优先级排序
    }
}

// ✅ 正确做法2 - 顺序观察链
public class OrderedEventBus {
    public void publish(Event event, EventListener... listeners) {
        for (EventListener listener : listeners) {
            listener.onEvent(event);  // 按指定顺序执行
        }
    }
}

// ✅ 正确做法3 - 异步处理避免时序问题
public class AsyncEventBus {
    private ExecutorService executor = Executors.newFixedThreadPool(4);
    
    public void publishAsync(Event event) {
        executor.submit(() -> {
            // 异步处理,避免阻塞
        });
    }
}

问题 3: 性能问题 - 观察者过多

现象: 观察者增加后,通知性能明显下降

原因: 需要遍历所有观察者,如果观察者处理缓慢会阻塞发布者

解决方案:

java
// ❌ 同步通知可能阻塞
for (Observer observer : observers) {
    observer.update(event);  // 如果某个观察者卡住,会阻塞所有后续观察者
}

// ✅ 使用线程池异步通知
ExecutorService executor = Executors.newFixedThreadPool(4);
for (Observer observer : observers) {
    executor.submit(() -> observer.update(event));
}

// ✅ 分组通知 - 将观察者分组,不同组并行处理
Map<String, List<Observer>> groupedObservers = observers.stream()
    .collect(Collectors.groupingBy(Observer::getGroup));

groupedObservers.forEach((group, listeners) -> {
    executor.submit(() -> {
        for (Observer observer : listeners) {
            observer.update(event);
        }
    });
});

// ✅ 性能计数 - 监控观察者执行时间
long startTime = System.nanoTime();
observer.update(event);
long duration = System.nanoTime() - startTime;
if (duration > 1_000_000_000) {  // 超过1秒
    logger.warn("观察者 {} 执行耗时 {}ms", observer.getClass(), duration / 1_000_000);
}

问题 4: 异常处理

现象: 一个观察者抛出异常,其他观察者不被通知

解决方案:

java
// ❌ 错误做法 - 异常会中断后续通知
for (Observer observer : observers) {
    observer.update(event);  // 如果抛出异常,后续优先级下降的观察者无法执行
}

// ✅ 正确做法 - 为每个观察者独立处理异常
for (Observer observer : observers) {
    try {
        observer.update(event);
    } catch (Exception e) {
        logger.error("观察者 {} 处理事件时出错", observer.getClass().getName(), e);
        // 继续处理其他观察者
    }
}

// ✅ 最佳实践 - 使用异步处理隔离异常
ExecutorService executor = Executors.newCachedThreadPool();
for (Observer observer : observers) {
    executor.submit(() -> {
        try {
            observer.update(event);
        } catch (Exception e) {
            logger.error("观察者异常", e);
        }
    });
}

最佳实践 TOP 5

1. ✅ 明确发布者和订阅者的职责

java
// 发布者 - 仅发布事件,不关心订阅者
public class UserService {
    private EventBus eventBus;
    
    public void registerUser(String username) {
        // ... 注册逻辑 ...
        eventBus.publish(new UserRegisteredEvent(username));
    }
}

// 订阅者 - 监听相关事件
eventBus.subscribe(UserRegisteredEvent.class, event -> {
    // 发送邮件等后续操作
    sendWelcomeEmail(event.username);
});

2. ✅ 使用强类型事件,避免字符串键

java
// ❌ 避免这样做 - 容易出错
eventBus.publish("user.registered", userData);

// ✅ 使用强类型
eventBus.publish(new UserRegisteredEvent(userId));

3. ✅ 使用异步处理防止阻塞

java
// 异步发布
public void publishAsync(Event event) {
    executor.submit(() -> {
        try {
            publish(event);
        } catch (Exception e) {
            logger.error("事件发布异常", e);
        }
    });
}

4. ✅ 提供反注册机制,防止内存泄漏

java
// 使用 AutoCloseable 简化反注册
try (Subscription sub = eventBus.subscribe(UserEvent.class, listener)) {
    // 在这个作用域内监听事件
}  // 自动取消订阅

5. ✅ 监控和日志

java
public class LoggingEventBus extends EventBus {
    @Override
    public void publish(Event event) {
        logger.info("发布事件: {}", event.getClass().getSimpleName());
        long startTime = System.nanoTime();
        super.publish(event);
        long duration = System.nanoTime() - startTime;
        logger.info("事件处理耗时: {}ms", duration / 1_000_000);
    }
}

与其他模式的关系

模式关系场景
Mediator中介者是观察者的特例(中央控制)复杂对象间的通信
Command可结合使用(命令作为事件)命令队列+事件通知
Strategy观察者可使用不同策略处理灵活事件处理
Model-ViewMVC 中核心模式模型变更自动更新视图

性能对比

实现方式通知延迟内存占用易用性推荐场景
推模式最短中等⭐⭐⭐简单事件
拉模式较长最低⭐⭐性能关键
事件总线中等中等⭐⭐⭐现代应用

何时应避免使用

❌ 以下情况不应使用观察者:

  1. 同步命令式调用足够

    java
    // ✅ 简单场景,直接调用更清晰
    userService.register(user);
    emailService.send(user.getEmail());
  2. 观察者链过长或循环依赖

    java
    // ❌ 避免:A→B→C→...→A 的循环
  3. 需要精确控制执行顺序

    java
    // ❌ 观察者顺序不确定,不适合

相关文件:


## 典型应用场景

### 1. GUI 事件系统
```java
button.addEventListener("click", event -> {
    // 处理点击
});

2. MVC 框架

java
// 模型变化通知视图
dataModel.addListener(view);

3. 异步任务通知

java
task.addProgressListener(progressBar::update);

最佳实践

  1. ✅ 使用弱引用避免内存泄漏
  2. ✅ 提供 add/remove 观察者方法
  3. ✅ 异常观察者不影响其他观察者
  4. ✅ 文档说明通知顺序和内容

Java 内置观察者

Observable/Observer

java
public class Model extends Observable {
    public void setState(String state) {
        setChanged();
        notifyObservers(state);
    }
}

PropertyChangeListener

java
bean.addPropertyChangeListener("name", evt -> {
    System.out.println("Name changed to " + evt.getNewValue());
});

何时避免使用

  • 只有一个发布者和订阅者
  • 通知关系简单
  • 不需要解耦

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the 观察者模式 AI skill do?

定义对象间一对多的依赖关系,当对象状态改变时通知所有依赖者。在设计事件系统、MVC框架、响应式编程等场景中使用。

Why use 观察者模式 on TypingMind?

Because you install it once and use it with any model. 观察者模式 is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install 观察者模式 in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/microwind/ai-skills/tree/main/design-patterns/observer-pattern. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use 观察者模式?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with 观察者模式?

As many as you like. As long as a model supports skills, you can use 观察者模式 with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the 观察者模式 AI skill free?

Yes. It is published on GitHub by microwind under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇