在C#中,观察者设计模式(Observer Pattern) 是一种行为型设计模式,用于定义对象间的"一对多"依赖关系:当一个对象(称为"主题/Subject")的状态发生变化时,所有依赖它的对象(称为"观察者/Observer")会自动收到通知并更新。这种模式的核心是解耦主题与观察者,让两者可以独立变化和复用。
核心角色
观察者模式包含4个核心角色:
- 主题(Subject):维护观察者列表,提供添加、移除观察者的方法,以及通知所有观察者的方法。
- 观察者(Observer):定义一个更新接口,当收到主题通知时执行具体的更新逻辑。
- 具体主题(Concrete Subject):实现主题接口,维护自身状态,状态变化时触发通知。
- 具体观察者(Concrete Observer):实现观察者接口,定义收到通知后的具体行为(如更新数据、刷新UI等)。
一、自定义观察者模式实现(基础版)
下面通过一个"气象站发布温度数据,多个显示设备接收并展示"的场景,演示自定义实现:
1. 定义接口(抽象层)
先定义主题和观察者的接口,明确规范:
csharp
// 观察者接口:定义更新方法
public interface IObserver
{
// 接收主题通知时调用,参数为主题传递的数据(这里是温度)
void Update(float temperature);
}
// 主题接口:定义管理观察者和通知的方法
public interface ISubject
{
// 添加观察者
void RegisterObserver(IObserver observer);
// 移除观察者
void RemoveObserver(IObserver observer);
// 通知所有观察者
void NotifyObservers();
}
2. 实现具体主题(气象站)
具体主题维护自身状态(温度)和观察者列表,状态变化时触发通知:
csharp
// 具体主题:气象站(发布温度数据)
public class WeatherStation : ISubject
{
// 维护观察者列表(使用泛型集合便于管理)
private List<IObserver> _observers = new List<IObserver>();
// 主题的状态:当前温度
private float _temperature;
// 添加观察者
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
// 移除观察者
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
// 通知所有观察者(触发它们的Update方法)
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(_temperature); // 传递当前温度
}
}
// 模拟温度变化(外部调用此方法更新状态,随后自动通知观察者)
public void SetTemperature(float temperature)
{
_temperature = temperature;
Console.WriteLine($"气象站:温度更新为 {temperature}℃");
NotifyObservers(); // 温度变化后立即通知所有观察者
}
}
3. 实现具体观察者(显示设备)
不同的观察者接收通知后执行不同的更新逻辑(如控制台显示、UI刷新等):
csharp
// 具体观察者1:控制台显示器
public class ConsoleDisplay : IObserver
{
public void Update(float temperature)
{
Console.WriteLine($"控制台显示:当前温度是 {temperature}℃");
}
}
// 具体观察者2:手机APP显示器
public class PhoneAppDisplay : IObserver
{
public void Update(float temperature)
{
Console.WriteLine($"手机APP提示:温度已更新至 {temperature}℃,请注意防暑/保暖!");
}
}
4. 客户端使用
客户端负责创建主题和观察者,并建立关联:
csharp
class Program
{
static void Main(string[] args)
{
// 创建主题(气象站)
WeatherStation weatherStation = new WeatherStation();
// 创建观察者(显示设备)
IObserver consoleDisplay = new ConsoleDisplay();
IObserver phoneAppDisplay = new PhoneAppDisplay();
// 注册观察者(观察者订阅主题)
weatherStation.RegisterObserver(consoleDisplay);
weatherStation.RegisterObserver(phoneAppDisplay);
// 模拟温度变化(触发通知)
weatherStation.SetTemperature(25.5f);
// 输出:
// 气象站:温度更新为 25.5℃
// 控制台显示:当前温度是 25.5℃
// 手机APP提示:温度已更新至 25.5℃,请注意防暑/保暖!
// 移除一个观察者(手机APP取消订阅)
weatherStation.RemoveObserver(phoneAppDisplay);
Console.WriteLine("\n手机APP已取消订阅");
// 再次更新温度
weatherStation.SetTemperature(30.0f);
// 输出:
// 气象站:温度更新为 30.0℃
// 控制台显示:当前温度是 30.0℃
}
}
二、.NET内置的观察者模式(IObservable与IObserver)
.NET Framework 4.0及以上提供了标准的观察者模式接口 :IObservable<T>(主题)和IObserver<T>(观察者),无需自定义接口,更符合.NET规范。
IObservable<T>:主题接口,定义Subscribe方法(用于观察者订阅),返回IDisposable(用于取消订阅)。IObserver<T>:观察者接口,定义3个方法:OnNext(T value):接收主题发送的新数据;OnError(Exception error):接收主题发送的错误信息;OnCompleted():接收主题发送的"完成"通知(后续不再发送数据)。
用内置接口实现气象站示例
csharp
using System;
using System.Collections.Generic;
// 具体主题:气象站(实现IObservable<T>,T为温度数据类型)
public class WeatherStation : IObservable<float>
{
private List<IObserver<float>> _observers = new List<IObserver<float>>();
private float _temperature;
// 观察者订阅时调用,返回IDisposable用于取消订阅
public IDisposable Subscribe(IObserver<float> observer)
{
if (!_observers.Contains(observer))
{
_observers.Add(observer);
}
// 返回一个自定义的取消订阅器
return new Unsubscriber(_observers, observer);
}
// 模拟温度变化(触发通知)
public void SetTemperature(float temperature)
{
_temperature = temperature;
Console.WriteLine($"气象站:温度更新为 {temperature}℃");
// 通知所有观察者(调用OnNext)
foreach (var observer in _observers)
{
observer.OnNext(temperature);
}
}
// 自定义取消订阅器(实现IDisposable)
private class Unsubscriber : IDisposable
{
private List<IObserver<float>> _observers;
private IObserver<float> _observer;
public Unsubscriber(List<IObserver<float>> observers, IObserver<float> observer)
{
_observers = observers;
_observer = observer;
}
// 取消订阅时从列表中移除观察者
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
{
_observers.Remove(_observer);
}
}
}
}
// 具体观察者1:控制台显示器(实现IObserver<float>)
public class ConsoleDisplay : IObserver<float>
{
public void OnNext(float temperature)
{
Console.WriteLine($"控制台显示:当前温度是 {temperature}℃");
}
public void OnError(Exception error)
{
Console.WriteLine($"控制台错误:{error.Message}");
}
public void OnCompleted()
{
Console.WriteLine("控制台:气象站已停止发送数据");
}
}
// 客户端使用
class Program
{
static void Main(string[] args)
{
WeatherStation weatherStation = new WeatherStation();
ConsoleDisplay consoleDisplay = new ConsoleDisplay();
// 订阅(返回取消订阅器)
IDisposable unsubscriber = weatherStation.Subscribe(consoleDisplay);
// 温度更新
weatherStation.SetTemperature(28.0f);
// 输出:
// 气象站:温度更新为 28.0℃
// 控制台显示:当前温度是 28.0℃
// 取消订阅(调用Dispose)
unsubscriber.Dispose();
Console.WriteLine("\n控制台已取消订阅");
// 再次更新温度(控制台不再收到通知)
weatherStation.SetTemperature(32.0f);
// 输出:
// 气象站:温度更新为 32.0℃
}
}
适用场景
- 当一个对象的状态变化需要联动更新多个其他对象(如"发布-订阅"场景);
- 当需要解耦"数据发布者"和"数据消费者"(如UI组件与数据模型的联动);
- 当不确定未来会有多少观察者需要接收通知(如日志系统、消息通知)。
优点与注意事项
- 优点:解耦主题与观察者,两者可独立扩展;支持动态添加/移除观察者;符合"开闭原则"。
- 注意事项 :
- 多线程环境下需保证观察者列表的线程安全(如用
lock锁定集合操作); - 避免观察者与主题形成循环依赖(可能导致内存泄漏);
- 若观察者过多,通知可能耗时,可考虑异步通知优化。
- 多线程环境下需保证观察者列表的线程安全(如用
C#中的观察者模式是实现"事件驱动"和"松耦合"的重要手段,无论是自定义实现还是使用.NET内置接口,核心思想都是通过抽象接口解耦主题与观察者,提升系统的灵活性和可维护性。