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

7 篇文章 0 订阅
2 篇文章 0 订阅

  委托是事件的基础,欲了解事件,请先阅读拙作——【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] 维基百科-观察者模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值