C# 设计模式——观察者

在C#中,观察者设计模式(Observer Pattern) 是一种行为型设计模式,用于定义对象间的"一对多"依赖关系:当一个对象(称为"主题/Subject")的状态发生变化时,所有依赖它的对象(称为"观察者/Observer")会自动收到通知并更新。这种模式的核心是解耦主题与观察者,让两者可以独立变化和复用。

核心角色

观察者模式包含4个核心角色:

  1. 主题(Subject):维护观察者列表,提供添加、移除观察者的方法,以及通知所有观察者的方法。
  2. 观察者(Observer):定义一个更新接口,当收到主题通知时执行具体的更新逻辑。
  3. 具体主题(Concrete Subject):实现主题接口,维护自身状态,状态变化时触发通知。
  4. 具体观察者(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内置接口,核心思想都是通过抽象接口解耦主题与观察者,提升系统的灵活性和可维护性。

相关推荐
亿牛云爬虫专家3 小时前
中间件实现任务去重与精细化分发:设计模式与常见陷阱
设计模式·中间件·爬虫代理·数据抓取·商品信息·数据去重·电商搜索
YongCheng_Liang4 小时前
Windows CMD 常用命令:7 大核心模块速查指南(附实战场景)
运维·服务器·windows·1024程序员节
wkj0015 小时前
conda创建在指定目录创建项目
linux·windows·conda
李趣趣11 小时前
数据库字段类型bit容易被忽视的bug
c#·bug
虚行13 小时前
C#OPC客户端通信实操
c#
想学全栈的菜鸟阿董14 小时前
CrewAI 核心概念 团队(Crews)篇
windows
梦昼初DawnDream14 小时前
防火墙规则设置
linux·服务器·windows
消失的旧时光-194315 小时前
kmp需要技能
android·设计模式·kotlin