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<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) 的基础。
它支持更复杂的操作,如:
- 完成通知: `OnCompleted()`
- 错误处理: `OnError(Exception error)`
- 流式处理: 可以对事件流进行过滤、映射、节流等操作。
如果你的业务逻辑涉及大量异步数据流处理,建议深入研究 Rx.NET。
6. 总结
- 简单场景/GUI开发: 请直接使用 C# `event`。
- 跨语言架构/强类型约束: 使用 经典接口模式。
- 复杂异步数据流: 使用 `IObservable<T>` (Rx)。