聊一聊:设计模式——观察者模式

85 阅读4分钟

你好,我是风一样的树懒,一个工作十多年的后端专家,曾就职京东、阿里等多家互联网头部企业。公众号“吴计可师”,已经更新了近百篇高质量的面试相关文章,喜欢的朋友欢迎关注点赞


观察者模式分步解析


一、模式定义

观察者模式(Observer Pattern) 是一种行为型设计模式,允许对象(观察者)订阅另一个对象(主题)的状态变化,当主题状态改变时,自动通知所有观察者并触发更新操作。


二、核心组件

角色职责
Subject(主题)维护观察者列表,提供注册、删除和通知方法。
Observer(观察者)定义更新接口,接收主题状态变化通知。
ConcreteSubject(具体主题)存储状态,状态变化时通知观察者。
ConcreteObserver(具体观察者)实现更新方法,定义收到通知后的具体行为。

三、实现步骤

1. 定义观察者接口
public interface Observer {
    void update(String message);  // 推模式:直接传递数据
    // void update(Subject subject); // 拉模式:传递主题引用
}
2. 定义主题接口
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
3. 实现具体主题
public class NewsAgency implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String news;

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(news);  // 通知所有观察者
        }
    }

    public void setNews(String news) {
        this.news = news;
        notifyObservers();   // 状态变更时自动通知
    }
}
4. 实现具体观察者
public class Subscriber implements Observer {
    private String name;

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

    @Override
    public void update(String news) {
        System.out.println(name + " 收到新闻更新: " + news);
    }
}
5. 客户端使用
public class Client {
    public static void main(String[] args) {
        NewsAgency agency = new NewsAgency();
        
        Observer user1 = new Subscriber("张三");
        Observer user2 = new Subscriber("李四");
        
        agency.registerObserver(user1);
        agency.registerObserver(user2);
        
        agency.setNews("今日头条:Java 21正式发布!");
        // 输出:
        // 张三 收到新闻更新: 今日头条:Java 21正式发布!
        // 李四 收到新闻更新: 今日头条:Java 21正式发布!
        
        agency.removeObserver(user1);
        agency.setNews("快讯:Spring 6.1新特性预览");
        // 输出:
        // 李四 收到新闻更新: 快讯:Spring 6.1新特性预览
    }
}

四、模式变体

1. 推模型 vs 拉模型
模型实现方式适用场景
推模型主题将详细数据通过update()参数传递观察者需固定数据字段
拉模型观察者通过主题引用主动获取所需数据观察者需灵活选择数据
// 拉模型示例
public interface Observer {
    void update(Subject subject);
}

public class NewsAgency implements Subject {
    // ...
    public String getNews() { return news; }
}

public class Subscriber implements Observer {
    @Override
    public void update(Subject subject) {
        if (subject instanceof NewsAgency) {
            String news = ((NewsAgency) subject).getNews();
            System.out.println("拉取新闻: " + news);
        }
    }
}
2. 事件驱动架构
  • 自定义事件类型:封装状态变化细节
    public class NewsEvent {
        private String title;
        private String content;
        // 构造函数、Getter...
    }
    
    public interface Observer {
        void onNewsPublished(NewsEvent event);
    }
    

五、实际应用场景

  1. GUI事件处理

    • 按钮点击事件监听
    • 窗口大小调整监听
  2. 消息中间件

    • RabbitMQ、Kafka的发布-订阅模型
  3. 状态监控系统

    • 服务器资源使用率监控报警
  4. MVVM框架

    • 数据绑定与视图自动更新

六、性能优化策略

  1. 异步通知

    public void notifyObservers() {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (Observer o : observers) {
            executor.submit(() -> o.update(news));
        }
    }
    
  2. 批量更新

    public void setNewsBatch(List<String> newsList) {
        this.newsList = newsList;
        notifyObservers();
    }
    
  3. 观察者优先级

    public class NewsAgency implements Subject {
        private PriorityQueue<Observer> observers = 
            new PriorityQueue<>(Comparator.comparingInt(Observer::getPriority));
        // ...
    }
    

七、与发布-订阅模式对比

维度观察者模式发布-订阅模式
耦合度主题与观察者直接交互通过消息代理解耦
灵活性动态增减观察者支持更复杂的路由规则(主题过滤)
典型实现Java内置ObserverRedis Pub/Sub、MQTT协议

八、注意事项

  1. 内存泄漏

    • 确保及时调用removeObserver()
    • 使用弱引用(WeakReference)包装观察者
      public class NewsAgency implements Subject {
          private List<WeakReference<Observer>> observers = new ArrayList<>();
          // 注册时 observers.add(new WeakReference<>(o))
      }
      
  2. 循环通知

    • 避免观察者修改主题状态导致无限递归
  3. 线程安全

    • 使用CopyOnWriteArrayList实现线程安全列表
      private List<Observer> observers = new CopyOnWriteArrayList<>();
      

九、Java标准库应用

// 属性变更监听(替代已废弃的Observable)
public class NewsAgency {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    
    public void addListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }
    
    public void setNews(String newValue) {
        String oldValue = this.news;
        this.news = newValue;
        support.firePropertyChange("news", oldValue, newValue);
    }
}

// 观察者实现
newsAgency.addListener(evt -> {
    System.out.println("新闻变更: " + evt.getNewValue());
});

十、总结

观察者模式通过解耦主题与观察者,实现了松耦合的事件通知机制,其核心价值在于:

  • 动态订阅机制:运行时灵活管理观察者
  • 广播通信能力:一对多消息高效传递
  • 开闭原则支持:新增观察者无需修改主题

在实现时需注意:

  • 线程安全与性能优化
  • 内存泄漏预防
  • 推/拉模型选择

适用于需要实现事件驱动架构状态监控系统的场景,是现代软件设计中实现组件间通信的基础模式之一。

今天文章就分享到这儿,喜欢的朋友可以关注我的公众号,回复“进群”,可进免费技术交流群。博主不定时回复大家的问题。 公众号:吴计可师