====== C# 设计模式:观察者模式 (Observer Pattern) ====== 观察者模式(Observer Pattern)是一种**行为型设计模式**。它定义了对象之间的一对多依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。 在 C# 中,观察者模式非常重要,它是 GUI 编程、MVC 架构以及现代响应式编程(Reactive Extensions)的基础。 ===== 1. 核心概念 ===== 主要涉及两个角色: * **Subject (主题/被观察者)**: 维护一组观察者,提供添加(Attach)和删除(Detach)观察者的方法,并在状态改变时通知(Notify)它们。 * **Observer (观察者)**: 定义一个更新接口,用于在收到主题通知时更新自己。 ===== 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 _observers = new List(); // 内部状态 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 委托 // 这相当于自动实现了 Attach, Detach 和 List public event EventHandler 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 与 IObserver ===== 在 .NET Framework 4.0 以后,微软引入了 `System.IObservable` 和 `System.IObserver` 接口,这是**响应式编程 (Reactive Extensions - Rx)** 的基础。 它支持更复杂的操作,如: * **完成通知**: `OnCompleted()` * **错误处理**: `OnError(Exception error)` * **流式处理**: 可以对事件流进行过滤、映射、节流等操作。 如果你的业务逻辑涉及大量异步数据流处理,建议深入研究 **Rx.NET**。 ===== 6. 总结 ===== * **简单场景/GUI开发**: 请直接使用 **C# `event`**。 * **跨语言架构/强类型约束**: 使用 **经典接口模式**。 * **复杂异步数据流**: 使用 **`IObservable` (Rx)**。