事件
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
举一个生活中常见的例子。例如我们关注了某个博主,当他发布新动态的时候,所有关注者都会接受到消息推送,这个就是这个事件的例子----发布以及订阅。
例子
在下面这个例子中,我们先用常规的方式去实现一个,当玩家死亡时,其他对象要对此做出反应的行为需求。
using System;
/*
* 我们想实现当玩家死亡的时候显示成就和游戏结束
*/
namespace Delegate
{
/// <summary>
/// 玩家
/// </summary>
class Play
{
public void Die()
{
UserInterface UI = new UserInterface();
Achievements achievements = new Achievements();
Console.WriteLine("玩家死亡!");
UI.ShowUI();
achievements.ShowScore();
}
}
/// <summary>
/// 用户界面
/// </summary>
class UserInterface
{
public void ShowUI()
{
Console.WriteLine("游戏结束!");
}
}
/// <summary>
/// 成就
/// </summary>
class Achievements
{
public void ShowScore()
{
Console.WriteLine("您当前的分数为 XX");
}
}
public class EventSample
{
public static void Run()
{
Play play = new Play();
play.Die();
}
}
}
你可以看到,这个非常容易实现而且很简便。但是这个里面有几点不足,对于后面的维护性很差,同时耦合也很大。
不足之处
- 耦合很大:假如我一删除一个类,那么我的程序就挂掉了,运行直接报错。
- 拓展困难:现在我要添加一个新的对象,敌人,他也需要对角色死亡时做出反应,那么我就需要到Play::Die这个函数中去修改,这样做肯定是没有问题。但是日后继续添加,又有新的需求,那么这个Die这个函数就会日益庞大,多了非常多奇奇怪怪的引用,对于后面维护的人或者是你会完全不知道这些对象是在干嘛的。同时问题一也会马上显现出来。
- 依赖很强:依赖很强也是耦合大的原因之一,太多的依赖,太多的功能堆叠在一起,这个函数,这个类就会变的难以维护。
- 维护性差:当你需求变动,例如我现在不需要你玩家死亡的显示游戏结束这个UI了,那么你肯定需要去Play::Die这个函数中去删除相关代码的,一段还好说,万一一手残删错了,就很尴尬了。
理想情况
我们只希望当角色死亡的时候,只需要简单的告诉我们就行了
举个不太恰当的例如。就好比,你在人群中,大喊一句,在场的各位都是**。那么在场的人想打你的肯定会自己过去揍你,而不用你自己去找他们。
using System;
/*
* 我们想实现当玩家死亡的时候显示成就和游戏结束
*/
namespace EventPro
{
public delegate void PlayerDeathDelegate();
/// <summary>
/// 玩家
/// </summary>
class Play
{
// 当玩家死亡时
public PlayerDeathDelegate onPlayDeath;
public void Die()
{
Console.WriteLine("玩家死亡!");
if (onPlayDeath != null)
onPlayDeath();
}
}
/// <summary>
/// 用户界面
/// </summary>
class UserInterface
{
public void ShowUI()
{
Console.WriteLine("游戏结束!");
}
}
/// <summary>
/// 成就
/// </summary>
class Achievements
{
public void ShowScore()
{
Console.WriteLine("您当前的成就为 XX");
}
}
/// <summary>
/// 敌人
/// </summary>
class Enemy
{
public void StopFire()
{
Console.WriteLine("停止攻击!");
}
}
public class EventSample
{
public static void Run()
{
/*
* UserInterface和Achievements已经和Play解耦,例如现在我们删除UserInterface也不会影响到其他的对象
*/
Play play = new Play();
UserInterface userInterface = new UserInterface();
Achievements achievements = new Achievements();
play.onPlayDeath += userInterface.ShowUI;
play.onPlayDeath += achievements.ShowScore;
// 新增需求,我们要玩家死亡时候,敌人停止攻击
Enemy enemy = new Enemy();
play.onPlayDeath += enemy.StopFire;
play.Die();
}
}
}
但是有个问题,就是我们到现在用的还是委托啊,还没有用到事件啊。为了引出事件,我们必须要明白这段代码的几个问题。因为这个代码也是存在问题的。
理想情况之后的问题
首先,可以肯定的是,它是解耦了,维护性也提高了。问题就在于,在之前的委托篇中,我们的委托是可以被覆盖的啊(委托中的=操作符)。而且还可以在外部直接调用onPlayDeath(手贱党或者不熟悉的人都可能会出现的情况)。那么你可能会这样想。
- 我谨慎点不就行了么
- 我设置为private不就行了么
问题就在于在谨慎也会出错,墨菲定律听说过没有。设为private外部也就无法访问了,直接over。那么我们该如何避免这些问题?
是时候祭出事件大法Event
运用Event
事件(Event)它解决了两个问题,在某些场景委托无法避免的问题。
- 防止委托(事件)被覆盖
- 防止出现多个调用者,这个请结合上面的例子
把上面的代码添加一个字段就行
class Play
{
// 当玩家死亡时
public event PlayerDeathDelegate onPlayDeath;
public void Die()
{
Console.WriteLine("玩家死亡!");
if (onPlayDeath != null)
onPlayDeath();
}
}
这样不管你在怎么手残,你都无法对该委托进行破坏。其实从这里可以看出C#语言开发者的用心,只需简单的添加event字段就可以了。因此我们在了解了某些场景委托的弊端之后,再去了解事件,就会发现区分委托和事件也就简单。
添加之后可以试试看使用覆盖和调用,这个时候编译器就会直接报错。