前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.Net中观察者模式(Observer):C#事件

.Net中观察者模式(Observer):C#事件

作者头像
小蜜蜂
发布2019-07-15 16:03:50
7480
发布2019-07-15 16:03:50
举报
文章被收录于专栏:明丰随笔

奥运会参加百米的田径运动员听到枪声,比赛立即进行。其中枪声是事件,而运动员比赛就是这个事件发生后的动作。不参加该项比赛的人对枪声没有反应。

从程序的角度分析,假设这个场景里面有一个裁判(Referee),二个参赛运动员(Athlete),裁判开枪,之后会发出枪响,运动员听到枪响立刻跑步。我们抽象出:

1. 裁判:Referee

2. 裁判开枪:Shoot

3. 发出枪响:Gunshot

4. 运动员:Athlete

5. 运动员跑步:Run

这个场景可以用典型的观察者模式来实现,裁判(publisher)他会开枪发出枪响,所有的运动员(subscriber)听到枪响立刻跑步。我们使用委托来实现这个功能。

首先定义Referee:

代码语言:javascript
复制
/// <summary>
/// 裁判
/// </summary>
public class Referee
{
    //裁判名字
    private string _name;

    public Referee(string name)
    {
        _name = name;
    }

    //定义枪声委托
    public delegate void GunshotDelegate();
    //声明枪声委托变量,所有运动员必须订阅这个变量,将来才可以听到“枪响”。
    public GunshotDelegate Gunshot;

    //裁判开枪
    public void Shoot()
    {
        Console.WriteLine(_name+" shoot start the game:");
        //枪响,所有聆听枪声的运动员开始跑步。(发布)
        Gunshot();
    }
}

然后定义Athlete:

代码语言:javascript
复制
/// <summary>
/// 运动员
/// </summary>
public class Athlete
{
    //名字
    private string _name;

    public Athlete(string name)
    {
        _name = name;
    }

    //跑步
    public void Run()
    {
        Console.WriteLine(_name + " begin to run.");
    }
}

在客户端代码调试:

代码语言:javascript
复制
//初始化2位运动员
Athlete athlete1 = new Athlete("Athlete1");
Athlete athlete2 = new Athlete("Athlete2");
//初始化裁判
Referee referee = new Referee("Referee");
//运动员聆听枪声(订阅)
referee.Gunshot = athlete1.Run;
referee.Gunshot += athlete2.Run;
//裁判开枪(发布)
referee.Shoot();

我们对这个代码进行一个简单总结,在Observer设计模式中,主要包括两类对象:publisher和subscriber。

1. publisher并不需要关心有多少subscriber。

2. subscriber也不需要知道publisher什么时候会发布订阅。

3. 松耦合管理对象间的一种一对多的依赖关系。

4. 当publisher对象的状态改变时,subscriber对象会被自动告知并更新。

但是我们的用委托来实现存在不足。

方法注册不一致:

代码语言:javascript
复制
//运动员聆听枪声(订阅)
Referee referee = new Referee("Referee");
referee.Gunshot = athlete1.Run;
referee.Gunshot += athlete2.Run;

第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别。

public的委托字段封装性不好

下面这两种方式都可以让比赛开始,但这不是我们愿意看到的,在客户端可以对它进行随意的赋值和调用等操作,严重破坏对象的封装性。

代码语言:javascript
复制
referee.Shoot();//调用开枪方法
referee.Gunshot();//直接调用枪声委托字段

如果把委托字段定义成private,客户端对它根本就不可见,所以必须手动显示实现委托的Add和Remove方法,比较麻烦。

所以因为这些缺点,我们才有了event关键字。它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。但是通常我们都声明public。我们修改上面的代码:

代码语言:javascript
复制
//声明枪声委托变量,所有运动员必须订阅这个变量,将来才可以听到“枪响”
public event GunshotDelegate Gunshot;

事件的声明与之前委托变量的声明唯一的区别是多了一个event关键字。但是却解决了之前我们越到的问题:

1.赋值问题

代码语言:javascript
复制
referee.Gunshot = athlete1.Run; //编译错误

修改为:

代码语言:javascript
复制
referee.Gunshot += athlete1.Run;

2.封装问题

代码语言:javascript
复制
referee.Gunshot();//编译错误,不可以访问private成员

使用反编译工具查看event关键字背后的秘密:

事件声明之后的委托被编译成私有字段,并同时生成了Add和Remove方法。这两个方法分别用于注册委托类型的方法和取消注册。实际上也就是: “+= ”对应 add_XXX,“-=”对应remove_XXX。在add_XXX()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。

.Net Framework中的委托与事件

尽管上面的范例很好地完成了我们想要完成的工作,但是我们不仅疑惑:为什么.Net Framework 中的事件模型和上面的不同?为什么有很多的EventArgs参数?

我们先搞懂 .Net Framework的编码规范:

1.委托类型的名称都应该以EventHandler结束。

2.委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object类型,一个EventArgs类型(或继承自EventArgs)。

3.事件的命名为 委托去掉 EventHandler之后剩余的部分。

4.继承自EventArgs的类型应该以EventArgs结尾。

5.委托声明原型中的Object类型的参数代表了sender,就是publisher。

6.EventArgs对象包含了subscriber所感兴趣的数据。

现在我们改写之前的范例,让它符合 .Net Framework 的规范:

代码语言:javascript
复制
/// <summary>
/// 裁判
/// </summary>
public class Referee
{
  //裁判名字
  private string _name;

  public Referee(string name)
  {
    _name = name;
  }

  //定义枪声委托
  public delegate void GunshotEventHandler(object sender, EventArgs args);
  //声明枪声委托变量,所有运动员必须订阅这个变量,将来才可以听到“枪响”
  public event GunshotEventHandler Gunshot;

  //裁判开枪
  public void Shoot()
  {
    Console.WriteLine(_name+" shoot start the game:");
    //枪响,所有聆听枪声的运动员开始跑步
    Gunshot(this, EventArgs.Empty);
  }
}

/// <summary>
/// 运动员
/// </summary>
public class Athlete
{
  //名字
  private string _name;

  public Athlete(string name)
  {
    _name = name;
  }

  //跑步
  public void Run(object sender, EventArgs args)
  {
    Console.WriteLine(_name + " begin to run.");
  }
}

static void Main(string[] args)
{
  //初始化2位运动员
  Athlete athlete1 = new Athlete("Athlete1");
  Athlete athlete2 = new Athlete("Athlete2");
  //初始化裁判
  Referee referee = new Referee("Referee");
  //运动员聆听枪声
  referee.Gunshot += athlete1.Run;
  referee.Gunshot += athlete2.Run;
  //裁判开枪
  referee.Shoot();

  Console.ReadKey();
}

输出为:

总结

通过文章学到了委托作为字段来实现观察者模式的不足,使用event可以改善,以及.Net Framework的事件编码规范。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档