Menu
CoddyTech

观察者模式(事件)

本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 54 课,共 70 课。

观察者模式建立了一对多关系:一个对象(主题)在状态发生变化时,会通知多个依赖对象(观察者)。在 C# 中,事件提供了一种内置方式,可以优雅地实现这一模式。

这种模式涉及一个发布者,它会引发事件,以及对事件作出响应的订阅者。下面是一个股票价格监控器的实际示例:

public class Stock
{
    private decimal _price;
    
    public event Action<decimal> PriceChanged;
    
    public decimal Price
    {
        get => _price;
        set
        {
            _price = value;
            PriceChanged?.Invoke(_price);  // 通知所有观察者
        }
    }
}

public class PriceAlert
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Alert: Price is now {newPrice}");
    }
}

public class PriceLogger
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Logged: {newPrice}");
    }
}

订阅者使用 += 将自己的方法附加到事件上,也可以使用 -= 将其移除:

var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();

stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;

stock.Price = 150.50m;
// 输出:
// 提醒:价格现在是 150.50
// 已记录:150.50

stock.PriceChanged -= logger.OnPriceChanged;  // 取消订阅
stock.Price = 155.00m;
// 输出:提醒:价格现在是 155.00

观察者模式有助于实现松耦合。Stock 类不需要了解任何关于观察者的信息。新的观察者可以订阅,而无需修改发布者,这让系统更易于扩展和维护。

challenge icon

挑战

简单

让我们使用事件和观察者模式来构建一个温度监控系统。你将创建一个传感器,每当温度发生变化时,它都会通知多个观察者。这非常适合气候控制系统等场景,因为其中的不同组件都需要响应温度更新。

你将把代码分成三个文件:

  • TemperatureSensor.cs:在 Monitoring 命名空间中创建一个 TemperatureSensor 类。这是你的发布者,也就是其他对象要观察的主体。传感器应包含:
    • 一个用于存储当前温度的私有字段
    • 一个名为 TemperatureChanged 的事件,它使用 Action<double> 通知订阅者新的温度
    • 一个 Temperature 属性,每当温度被设为新值时都会触发该事件
  • Observers.cs:在同一命名空间中创建两个观察者类,用于订阅温度变化:
    • DisplayUnit:包含一个 OnTemperatureChanged(double temp) 方法,用于输出 Display: {temp}C
    • AlarmSystem:包含一个 OnTemperatureChanged(double temp) 方法,用于输出 Alarm Check: {temp}C
  • Program.cs:创建一个传感器和两个观察者,将它们连接起来。让两个观察者都订阅传感器的事件,然后更新温度。之后,取消订阅报警系统,再次更新温度,以显示只有显示单元会收到通知。

你将收到两个输入:

  • 第一个温度读数(例如,22.5)
  • 第二个温度读数(例如,28.0)

你的程序应当:

  1. 创建传感器和两个观察者
  2. 让两个观察者都订阅 TemperatureChanged 事件
  3. 将传感器的温度设为第一个输入值(两个观察者都应收到通知)
  4. 取消 AlarmSystem 对事件的订阅
  5. 将传感器的温度设为第二个输入值(只有显示单元应收到通知)

例如,如果输入为 22.5 和 28.0,输出应为:

Display: 22.5C
Alarm Check: 22.5C
Display: 28C

这展示了观察者模式的核心优势:传感器不知道也不关心谁在监听它。观察者可以动态订阅和取消订阅,而发布者只需通知当前正在监听的对象!

亲手试试

using System;
using Monitoring;

class Program
{
    public static void Main(string[] args)
    {
        // 读取输入的温度
        double temp1 = Convert.ToDouble(Console.ReadLine());
        double temp2 = Convert.ToDouble(Console.ReadLine());

        // TODO: 创建一个 TemperatureSensor 实例

        // TODO: 创建 DisplayUnit 和 AlarmSystem 实例

        // TODO: 让两个观察者都订阅 TemperatureChanged 事件

        // TODO: 将传感器的温度设置为 temp1(两个观察者都会收到通知)

        // TODO: 让 AlarmSystem 取消订阅该事件

        // TODO: 将传感器的温度设置为 temp2(只有显示单元会收到通知)
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C# 在线编译器