C# 设计模式——观察者
在C#中,观察者设计模式(Observer Pattern) 是一种行为型设计模式,用于定义对象间的“一对多”依赖关系:当一个对象(称为“主题/Subject”)的状态发生变化时,所有依赖它的对象(称为“观察者/Observer”)会自动收到通知并更新。这种模式的核心是解耦主题与观察者,让两者可以独立变化和复用。
核心角色
观察者模式包含4个核心角色:
- 主题(Subject):维护观察者列表,提供添加、移除观察者的方法,以及通知所有观察者的方法。
- 观察者(Observer):定义一个更新接口,当收到主题通知时执行具体的更新逻辑。
- 具体主题(Concrete Subject):实现主题接口,维护自身状态,状态变化时触发通知。
- 具体观察者(Concrete Observer):实现观察者接口,定义收到通知后的具体行为(如更新数据、刷新UI等)。
一、自定义观察者模式实现(基础版)
下面通过一个“气象站发布温度数据,多个显示设备接收并展示”的场景,演示自定义实现:
1. 定义接口(抽象层)
先定义主题和观察者的接口,明确规范:
// 观察者接口:定义更新方法
public interface IObserver
{// 接收主题通知时调用,参数为主题传递的数据(这里是温度)void Update(float temperature);
}// 主题接口:定义管理观察者和通知的方法
public interface ISubject
{// 添加观察者void RegisterObserver(IObserver observer);// 移除观察者void RemoveObserver(IObserver observer);// 通知所有观察者void NotifyObservers();
}
2. 实现具体主题(气象站)
具体主题维护自身状态(温度)和观察者列表,状态变化时触发通知:
// 具体主题:气象站(发布温度数据)
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刷新等):
// 具体观察者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. 客户端使用
客户端负责创建主题和观察者,并建立关联:
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()
:接收主题发送的“完成”通知(后续不再发送数据)。
用内置接口实现气象站示例
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内置接口,核心思想都是通过抽象接口解耦主题与观察者,提升系统的灵活性和可维护性。