====== 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)**。