C# 委托与事件实现观察者模式详解

小满
小满
发布时间:2026-10-15 | 2人浏览过
原创

在软件设计中,当某个对象的动作需要通知多个其他对象时,传统的直接调用会导致代码耦合度过高。本文将基于一个宠物追踪器离界报警的场景,展示如何在 C# 中使用委托(Delegate)和事件(Event)重构代码,实现标准的观察者模式,使主体对象无需关心具体观察者的实现细节。

问题背景:紧耦合的单体实现

假设我们需要开发一个宠物追踪系统:当宠物离开主人超过 100 米时,主人的终端设备需要同时执行两个操作——发出声音警报并在屏幕上显示文字警告。在最简单的实现中,所有逻辑都集中在同一个类中:

public class PetTracker
{
    private int distance;

    // 监控逻辑
    public void InstanceTrack()
    {
        for (int i = 0; i < 102; i++)
        {
            distance = i;
            if (distance > 100)
            {
                // 直接调用内部方法,耦合度高
                MakeAlert(distance);
                ShowAlert(distance);
            }
        }
    }

    // 发出警报声
    private void MakeAlert(int param)
    {
        Console.WriteLine("嘀嘀嘀,您的宝贝已经离你" + param + "米之外了,要注意哦~~");
    }

    // 显示报警信息
    private void ShowAlert(int param)
    {
        Console.WriteLine("您的宝贝已经离你" + param + "米之外了,要注意哦~~");
    }
}

// 调用示例
class Program
{
    static void Main(string[] args)
    {
        PetTracker tracker = new PetTracker();
        tracker.InstanceTrack();
        Console.ReadKey();
    }
}

运行结果如图所示:

虽然这种写法简单直观,但它将“监测”、“发声”和“显示”紧紧捆绑在一起。如果未来需要增加震动反馈、发送手机推送或修改声音策略,必须修改 `PetTracker` 类的源码,这违反了开闭原则。为了解决这个问题,我们将不同功能拆分为独立的类,并引入观察者模式。

第一步:拆分职责,识别角色

我们将原有的逻辑拆分为三个独立的类: 1. PetTracker:作为被监视对象(Subject),负责监测距离。 2. MakerAlertSupplier:作为观察者之一,负责声音报警。 3. ShowAlertSupplier:作为观察者之二,负责界面显示。

public class PetTracker
{
    private int distance;

    public void InstanceTrack()
    {
        for (int i = 0; i < 102; i++)
        {
            distance = i;
            if (distance > 100)
            {
                // 此时如何通知外部观察者?
            }
        }
    }   
}

public class MakerAlertSupplier
{
    public void MakeAlert(int param)
    {
        Console.WriteLine("嘀嘀嘀,您的宝贝已经离你" + param + "米之外了,要注意哦~~");
    }
}

public class ShowAlertSupplier
{
    public static void ShowAlert(int param)
    {
        Console.WriteLine("您的宝贝已经离你" + param + "米之外了,要注意哦~~");
    }
}

现在面临核心问题:PetTracker 在检测到异常时,该如何动态地通知任意数量的观察者,而不需要硬编码具体的类名?答案就是使用委托(Delegate)。

第二步:定义委托类型

委托是方法的类型安全引用。由于所有观察者的处理方法都需要接收一个 int 类型的参数(即距离),且返回值均为 void,我们可以定义一个统一的委托签名:

在 PetTracker 内部定义:
public delegate void TrackHandler(int param);

这个委托定义了所有符合该签名的方法都可以被“订阅”。它起到了契约的作用,确保了参与者的方法结构一致。

第三步:声明并触发事件

委托本身只是一个类型,我们需要一个变量来存储当前被通知的方法列表。在 C# 中,我们通常使用 event 关键字来包装委托,以提供更好封装性:

  1. 声明事件:在 PetTracker 中声明事件变量 public event TrackHandler TrackEvent;。
  2. 检查并触发:在 InstanceTrack 方法中,当距离大于 100 时,检查事件是否不为空,然后调用它。
public class PetTracker
{
    private int distance;

    // 1. 定义委托
    public delegate void TrackHandler(int param);
    // 2. 定义事件
    public event TrackHandler TrackEvent;

    public void InstanceTrack()
    {
        for (int i = 0; i < 102; i++)
        {
            distance = i;
            if (distance > 100)
            {
                // 3. 触发事件,通知所有注册的观察者
                if (TrackEvent != null)
                {
                    TrackEvent(distance);
                }
            }
        }
    }   
}

注意:这里使用 if (TrackEvent != null) 是为了防止在没有观察者订阅的情况下报错。

第四步:在主程序中订阅与执行

客户端(如 Main 方法)现在可以灵活地决定哪些对象需要响应警报。我们通过 += 运算符将观察者的方法绑定到 TrackEvent 上:

class Program
{
    static void Main(string[] args)
    {
        PetTracker tracker = new PetTracker();
        
        // 订阅声音警报(实例方法)
        tracker.TrackEvent += (new MakerAlertSupplier()).MakeAlert;
        
        // 订阅显示警报(静态方法)
        tracker.TrackEvent += ShowAlertSupplier.ShowAlert;
        
        // 启动追踪,内部会自动触发注册的方法
        tracker.InstanceTrack();
        
        Console.ReadKey();
    }
}

最终运行效果如下:

总结

通过以上步骤,我们成功实现了松耦合的观察者模式:

  • 委托抽象了定义:确保所有观察者具有相同的输入输出规范(int 参数,void 返回)。
  • 事件管理注册:使用 event 关键字允许外部通过 += / -= 安全地添加或删除监听者,而 PetTracker 类内部无法从外部直接被赋值覆盖。
  • 分离关注点:PetTracker 只负责监测和广播,不再关心声音或显示的具体实现。如果需要新增震动功能,只需创建一个新类实现对应方法并订阅即可,无需修改原有代码。

以上就是 C# 中委托和事件在观察者模式中的详细内容,更多关于 C# 设计模式与事件驱动编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表