目录

C# 设计模式:观察者模式 (Observer Pattern)

观察者模式(Observer Pattern)是一种行为型设计模式。它定义了对象之间的一对多依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

在 C# 中,观察者模式非常重要,它是 GUI 编程、MVC 架构以及现代响应式编程(Reactive Extensions)的基础。

1. 核心概念

主要涉及两个角色:

2. 操作方式一:经典接口实现 (Classic Approach)

这种方式是设计模式教科书中的标准做法,适用于理解原理或跨语言移植。

第一步:定义抽象接口

我们需要定义“观察者”和“主题”的契约。

using System;
using System.Collections.Generic;
 
namespace ObserverPatternDemo
{
    // 观察者接口
    public interface IObserver
    {
        // 当主题状态改变时,调用此方法
        void Update(string message);
    }
 
    // 主题接口
    public interface ISubject
    {
        void Attach(IObserver observer); // 订阅
        void Detach(IObserver observer); // 取消订阅
        void Notify();                   // 通知所有
    }
}

第二步:实现具体主题 (ConcreteSubject)

这是实际发生变化的数据源。

namespace ObserverPatternDemo
{
    public class NewsPublisher : ISubject
    {
        // 维护观察者列表
        private List<IObserver> _observers = new List<IObserver>();
 
        // 内部状态
        private string _latestNews;
 
        // 实现订阅
        public void Attach(IObserver observer)
        {
            _observers.Add(observer);
            Console.WriteLine("Subject: 增加了一个观察者。");
        }
 
        // 实现取消订阅
        public void Detach(IObserver observer)
        {
            _observers.Remove(observer);
            Console.WriteLine("Subject: 移除了一个观察者。");
        }
 
        // 实现通知逻辑
        public void Notify()
        {
            Console.WriteLine("Subject: 正在通知所有观察者...");
            foreach (var observer in _observers)
            {
                observer.Update(_latestNews);
            }
        }
 
        // 业务逻辑:发布新闻
        public void PublishNews(string news)
        {
            Console.WriteLine($"\nSubject: 发布新消息 -> {news}");
            _latestNews = news;
            Notify(); // 状态改变,触发通知
        }
    }
}

第三步:实现具体观察者 (ConcreteObserver)

namespace ObserverPatternDemo
{
    public class Subscriber : IObserver
    {
        private string _name;
 
        public Subscriber(string name)
        {
            _name = name;
        }
 
        public void Update(string message)
        {
            Console.WriteLine($"  Observer ({_name}): 收到新闻 -> {message}");
        }
    }
}

第四步:客户端调用

class Program
{
    static void Main(string[] args)
    {
        // 1. 创建主题
        var publisher = new NewsPublisher();
 
        // 2. 创建观察者
        var sub1 = new Subscriber("张三");
        var sub2 = new Subscriber("李四");
 
        // 3. 订阅
        publisher.Attach(sub1);
        publisher.Attach(sub2);
 
        // 4. 触发变化
        publisher.PublishNews("C# 12 发布了!");
 
        // 5. 取消订阅演示
        publisher.Detach(sub1);
        publisher.PublishNews("观察者模式很简单。");
    }
}

3. 操作方式二:C# 事件与委托 (Events & Delegates)

这是 C# 开发者最常用的方式。C# 语言层面内置了观察者模式的支持,无需手动维护列表和循环调用。

第一步:定义事件参数

虽然可以直接传 string,但标准的 .NET 模式建议继承 `EventArgs`。

public class NewsEventArgs : EventArgs
{
    public string Title { get; }
    public DateTime Time { get; }
 
    public NewsEventArgs(string title)
    {
        Title = title;
        Time = DateTime.Now;
    }
}

第二步:使用 EventHandler 定义主题

public class ModernPublisher
{
    // 声明事件:使用内置的 EventHandler<T> 委托
    // 这相当于自动实现了 Attach, Detach 和 List<Observer>
    public event EventHandler<NewsEventArgs> NewsPublished;
 
    public void Publish(string title)
    {
        Console.WriteLine($"发布者: 产生新闻 - {title}");
        OnNewsPublished(new NewsEventArgs(title));
    }
 
    // 标准的受保护虚方法,用于触发事件
    protected virtual void OnNewsPublished(NewsEventArgs e)
    {
        // ?.Invoke 检查是否有人订阅(是否为 null),如果不为 null 则调用
        NewsPublished?.Invoke(this, e);
    }
}

第三步:客户端订阅 (Lambda 或 方法)

class Program
{
    static void Main(string[] args)
    {
        var publisher = new ModernPublisher();
 
        // 观察者 1:使用 Lambda 表达式订阅
        publisher.NewsPublished += (sender, e) => 
        {
            Console.WriteLine($"  [Lambda观察者] 收到: {e.Title} 时间: {e.Time}");
        };
 
        // 观察者 2:使用传统方法订阅
        publisher.NewsPublished += LegacyHandler;
 
        // 触发
        publisher.Publish("学习 C# 事件");
 
        // 取消订阅 (使用 -=)
        publisher.NewsPublished -= LegacyHandler;
 
        publisher.Publish("只有 Lambda 观察者还在听");
    }
 
    static void LegacyHandler(object sender, NewsEventArgs e)
    {
        Console.WriteLine($"  [方法观察者] 处理: {e.Title}");
    }
}

4. 两种方式对比

特性 经典接口方式 (Interface) C# 事件方式 (Event)
耦合度 较高,观察者必须实现特定接口 低,只要方法签名匹配即可 (委托)
代码量 较多,需要手动管理列表 极少,编译器自动生成管理代码
灵活性 可以控制通知顺序,易于调试 顺序不保证,调试略复杂
内存泄漏 容易忘记 Detach 容易忘记 -= (这是常见坑点)

5. 进阶:IObservable<T> 与 IObserver<T>

在 .NET Framework 4.0 以后,微软引入了 `System.IObservable<T>` 和 `System.IObserver<T>` 接口,这是响应式编程 (Reactive Extensions - Rx) 的基础。

它支持更复杂的操作,如:

如果你的业务逻辑涉及大量异步数据流处理,建议深入研究 Rx.NET

6. 总结