C# 委托与事件实现观察者模式详解
在软件设计中,当某个对象的动作需要通知多个其他对象时,传统的直接调用会导致代码耦合度过高。本文将基于一个宠物追踪器离界报警的场景,展示如何在 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 关键字来包装委托,以提供更好封装性:
- 声明事件:在
PetTracker中声明事件变量public event TrackHandler TrackEvent;。 - 检查并触发:在
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# 设计模式与事件驱动编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













