首页 > 学院 > 开发设计 > 正文

【C#】事件与观察者模式

2019-11-06 07:04:51
字体:
来源:转载
供稿:网友

  委托是事件的基础,欲了解事件,请先阅读拙作——【C#】委托基础

【概述】

事件

  源于委托,可以理解为委托的一种形式。使用事件,当事件触发时,所有注册该事件的订阅者均会收到消息并发生改变。这里有两个新名词,一个是注册,一个是订阅者。注册事件与委托关联方法其实差不多,可以用理解委托关联方法的方式来理解注册事件。与注册相对应的便是注销,在委托的语法中相当于移除对方法的关联。   注册和注销分别用“+=”和“-=”操作符。这里有一点与委托不同的是,委托在关联多个方法时,需要对其赋值null或者方法,其后才可用“+=”和“-=”操作符;而事件创建实例后,可以直接注册或者注销方法。产生这种差异与事件这个类型本身内部机制有关,这里不做赘述。

观察者模式

  观察者模式是软件设计众多模式中的一种,是一种解决问题的方案。在此种模式中:当一个对象的状态发生改变时,会通知所有与此对象相关联的对象(观察者),并使用观察者提供(注册)的方法改变观察者的状态。简单地说,就是定义的方法自己不去调用,而是当引发某个事件后(由其他对象引发)此方法自动被响应,这个方法是跟事件紧密相关的。   举个例子,上学时老师都会留作业,那么“布置作业”就是一个事件,老师就是发起这个事件的对象,班级的每个同学都是不同的对象,他们都具有方法“写作业”,他们将“写作业”的方法注册到“布置作业”这个事件上,当老师留家庭作业时,就会引发“布置作业”事件,从而每个同学都开始写作业,即执行“写作业”方法。总的来说就是“写作业”这一行为由“布置作业”来决定,而布置作业的对象是老师,并不是学生本身。


【案例】

  为了更好地理解事件机制,我们在Unity中构建一个案例。

策划

对象:

  敌人,哨兵,守城军队,居民。

流程:

  当敌人军队被哨兵发现时,守城军队会出城抵御敌人,而居民会向后撤退。

方法设计:

  为了简化流程,用点击按钮的形式表示敌人接近,所以,敌人类只包含Name属性和构造方法即可。当敌人接近时,此时触发哨兵的方法“发现敌人”(DiscoverEnemy,并触发事件“敌人来了”(EnemyComing),此事件包含两个参数,一是敌人对象,二是敌人的位置;该事件被守城军队和居民注册,注册的方法分别为“御敌”(DefenceEnemy)和“撤退”(Retreat),当事件触发时会调用这两个方法。

实现

敌人类(Enemy):

public class Enemy{ public readonly string Name; public Enemy(string name) { Name = name; }}

哨兵类(Sentry):

using UnityEngine;using UnityEngine.UI;using System.Collections;public class Sentry : MonoBehaviour { public delegate void EnemyComingHandler(Enemy enemy, Vector3 position); public event EnemyComingHandler EnemyComing; public void DiscoverEnemy() { //如果有注册事件,才会触发事件 if (EnemyComing != null) //触发事件,通知所有订阅者 EnemyComing(new Enemy("Make"), new Vector3(1f, 2f, 3f)); }}

守卫队类(Guards):

using UnityEngine;using UnityEngine.UI;using System.Collections;public class Guards : MonoBehaviour { public Sentry MySentry; void Start () { AddEvent(); } //注册 void AddEvent() { MySentry.EnemyComing += new Sentry.EnemyComingHandler(DefenceEnemy); } //注销 void RemoveEvent() { MySentry.EnemyComing -= new Sentry.EnemyComingHandler(DefenceEnemy); } void DefenceEnemy(Enemy enemy, Vector3 enemyPosition) { Debug.Log("We are guard, we know the enemy named " + enemy.Name + " at the " + enemyPosition.ToString()); }}

居民类(Inhabitants):

public class Inhabitants : MonoBehaviour { public Sentry MySentry; void Start() { AddEvent(); } //注册 void AddEvent() { MySentry.EnemyComing += new Sentry.EnemyComingHandler(Retreat); } //注销 void RemoveEvent() { MySentry.EnemyComing -= new Sentry.EnemyComingHandler(Retreat); } void Retreat(Enemy enemy, Vector3 enemyPosition) { Debug.Log("We are inhabitants, we know the enemy named " + enemy.Name + " at the " + enemyPosition.ToString()); }}

场景设置:   1.在场景中创建三个空物体,分别表示哨兵,守卫,居民对象,并将脚本分别指定为给对应对象;   2.将哨兵对象指定给守卫与居民的Sentry属性;   3.创建Button,并关联哨兵的DiscoverEnemy方法。   4.点击Button,即可调用Retreat方法与DefenceEnemy方法。 这里写图片描述


【总结】

  在面向对象的程序设计中,模块化是基本,而模块之间往往需要“通信”,事件机制便是最常用的方式之一。事件的特点是“1拖n”牵引效应,它往往应用于模块之间因果关系明显的情况。通过事件机制,模块之间的耦合度降低,我不需要关心你的事件是如何发生的,你也不需要关系事件发生之后我会做些什么,我与你之间的联系不过是一个信号,而这个信号就是事件触发。事件机制也十分利用项目拓展,当有有新的模块的方法需要主程的某个事件时,只需注册这个事件,而不需要改变模块方法的访问级与主程代码,避免未知风险的发生。

【参考资料】

[1] 《Unity3D脚本编程》 陈嘉栋 著。 [2] 维基百科-观察者模式


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表