教程中心 / Java教程 / 观察者模式

观察者模式

10分钟 设计模式

什么是观察者模式?

观察者模式(Observer Pattern)是一种行为型设计模式,也叫"发布-订阅模式"。它的核心思想是:当一个对象(被观察者)的状态发生变化时,会自动通知所有依赖它的对象(观察者),无需主动轮询或回调。

生活比喻

想象你订阅了一个YouTube频道。当频道发布新视频时,你不需要每隔几分钟刷新一次页面——YouTube会自动推送通知给你。YouTube是被观察者(Subject),你是观察者(Observer)。你只订阅一次,之后有新内容就会自动收到通知。

观察者模式结构:

┌─────────────────────────────────────────────────────────────┐
│                    Subject(被观察者)                       │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  observers: List<Observer>                           │  │
│  │  attach(observer)      → 添加观察者                  │  │
│  │  detach(observer)      → 移除观察者                  │  │
│  │  notify()              → 通知所有观察者              │  │
│  └─────────────────────────────────────────────────────┘  │
└───────────────────────────┬─────────────────────────────────┘
                            │ notify
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
┌───────────┐       ┌───────────┐       ┌───────────┐
│Observer A │       │Observer B │       │Observer C │
│update()  │       │update()   │       │update()   │
└───────────┘       └───────────┘       └───────────┘

完整实现

1. 定义观察者接口

// 观察者接口 - 定义收到通知时的行为
public interface Observer {
    void update(String message);  // 收到更新时调用
}

2. 定义被观察者

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

// 被观察者 - 当状态变化时通知所有观察者
public class Subject {
    private String state;
    private List 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(this.state);
        }
    }

    // 设置状态(这会触发通知)
    public void setState(String state) {
        this.state = state;
        System.out.println("[Subject] 状态变为: " + state);
        notifyObservers();  // 状态变化,自动通知
    }

    public String getState() {
        return state;
    }
}

3. 创建具体观察者

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

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

    @Override
    public void update(String message) {
        System.out.println("[" + name + "] 收到通知: " + message);
    }
}

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

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

    @Override
    public void update(String message) {
        System.out.println("[" + name + "] 处理消息: " + message.toUpperCase());
    }
}

4. 使用

public class ObserverDemo {
    public static void main(String[] args) {
        Subject subject = new Subject();

        Observer alice = new ConcreteObserverA("Alice");
        Observer bob = new ConcreteObserverA("Bob");
        Observer charlie = new ConcreteObserverB("Charlie");

        System.out.println("===== 添加三个观察者 =====");
        subject.attach(alice);
        subject.attach(bob);
        subject.attach(charlie);

        System.out.println("\n===== 第一次状态更新 =====");
        subject.setState("START");

        System.out.println("\n===== Bob取消订阅 =====");
        subject.detach(bob);

        System.out.println("\n===== 第二次状态更新 =====");
        subject.setState("COMPLETE");
    }
}

实战案例:股票行情系统

import java.util.*;

// 股票(被观察者)
class Stock {
    private String symbol;
    private double price;
    private List investors = new ArrayList<>();

    public Stock(String symbol, double price) {
        this.symbol = symbol;
        this.price = price;
    }

    public void addInvestor(Investor investor) {
        investors.add(investor);
    }

    public void removeInvestor(Investor investor) {
        investors.remove(investor);
    }

    public void setPrice(double newPrice) {
        double oldPrice = this.price;
        this.price = newPrice;

        System.out.println("[" + symbol + "] 价格变动: $" + oldPrice + " → $" + newPrice);
        notifyInvestors();
    }

    private void notifyInvestors() {
        for (Investor investor : investors) {
            investor.update(this);
        }
    }

    public String getSymbol() { return symbol; }
    public double getPrice() { return price; }
}

// 投资者(观察者)
interface Investor {
    void update(Stock stock);
}

// 个人投资者
class IndividualInvestor implements Investor {
    private String name;
    private double threshold;

    public IndividualInvestor(String name, double threshold) {
        this.name = name;
        this.threshold = threshold;  // 价格变化超过这个阈值就买入/卖出
    }

    @Override
    public void update(Stock stock) {
        System.out.println("[" + name + "] 收到更新 - " + stock.getSymbol() +
            " 当前价格: $" + stock.getPrice());
    }
}

// 机构投资者
class InstitutionalInvestor implements Investor {
    private String name;

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

    @Override
    public void update(Stock stock) {
        System.out.println("[" + name + "] 机构交易系统收到更新");
        if (stock.getPrice() < 150) {
            System.out.println("  → 建议买入 " + stock.getSymbol());
        } else if (stock.getPrice() > 200) {
            System.out.println("  → 建议卖出 " + stock.getSymbol());
        }
    }
}

public class StockMarketDemo {
    public static void main(String[] args) {
        Stock apple = new Stock("AAPL", 175.50);

        Investor investor1 = new IndividualInvestor("张三", 5.0);
        Investor investor2 = new IndividualInvestor("李四", 10.0);
        Investor investor3 = new InstitutionalInvestor("高盛");

        apple.addInvestor(investor1);
        apple.addInvestor(investor2);
        apple.addInvestor(investor3);

        System.out.println("===== 第一次价格变动 =====");
        apple.setPrice(172.30);

        System.out.println("\n===== 第二次价格变动 =====");
        apple.setPrice(205.00);
    }
}

Java内置的观察者模式

Java早期提供了Observable类和Observer接口,但它们已被标记为@Deprecated(Java 9之后)。现在推荐自己实现或使用第三方库如Guava的EventBus。

观察者模式的优缺点

优点

松耦合:被观察者不需要知道观察者的具体类型
动态订阅:可以随时添加/移除观察者
自动通知:状态变化自动推送

缺点

内存泄漏:忘记取消订阅会导致泄漏
通知顺序:观察者收到通知的顺序不确定
循环依赖:复杂的观察关系可能导致循环

应用场景

场景被观察者观察者
消息推送消息中心订阅者
股票行情股票投资者
GUI事件按钮点击监听器
数据绑定数据模型UI组件
日志系统Logger日志处理器

注意事项

内存泄漏风险!

如果观察者不再使用但没有取消订阅,被观察者仍会持有观察者的引用,导致内存泄漏。解决方案:

  • 在不需要时调用detach()
  • 使用WeakHashMap存储观察者
  • 使用生命周期管理(如Android的onDestroy)

小结

  • 观察者模式实现一对多的依赖关系
  • 被观察者状态变化时,自动通知所有观察者
  • 可以动态添加/移除观察者
  • 遵循松耦合原则,被观察者不需要知道观察者的具体类型
  • 注意内存泄漏问题,及时取消订阅
☕ Java 在线代码编辑器
📝 运行结果