zoukankan      html  css  js  c++  java
  • 深入浅出话事件(上)

    小序

             在上篇文章(《深入浅出话委托》)中,我们集中讨论了什么是委托以及委托的用法。有朋友问:什么时候用委托——说实话,使用某种编程要素是一种思想,更是一种习惯。举个极端点的例子:比如你问我“什么时候使用for循环”,我完全可以回答——根本用不着for循环,用ifgoto就完全能够搞定——我们大多数人使用for循环,是因为我们认同for循环的思想,并且养成了使用for循环的习惯。委托也是这样——没有委托的日子,程序员们一样在干活,只是有了委托机制后,大家干起来更方便、写出的代码质量更高——当你体验到它的方便、自然而然地使用它、养成一种习惯后,你就知道什么时候应该使用它了。OK,我们回到正题上来,委托最常用的一个领域是用来声明“事件”,或者说:委托是事件的基础。作为《深入浅出话委托》的姊妹篇,本文我们主要来讨论事件(Event)。

    正文

    一.什么是事件

             程序语言是对人类语言的抽象,因此,程序语言要素往往与人类语言中对应的词汇有着相近的含义。正是这种“相近”,让很多要素看上去很好懂,但如果不仔细理解,就会误入歧途。“事件”就是这类要素中的一个,让我们小心对待。

    现实世界中的事件

             先让我们看一看现实世界中的“事件”。

             现实世界中的“事件”(Event)是指“有一定社会意义或影响的大事情”。 提取一下句子的二级主干,我们可以得出:事件=有意义或影响的事情。因此,我们可以看出,判定是否为一个“事件”有两个先决条件:首先它要是一个“事情”,然后这个事情还要“有意义或影响”。

             接着,我们进一步分析“事情”这个概念。我们常说“一件事情发生了”,这个“发生”组成要素又无外乎时间、地点、参与人物(主体)、所涉及的客体——抽象一点,我们可以把这些要素称为“事情”的参数。

             一件事情发生了,可能对某些客体(Client)产生影响,也可能没有任何影响。如果事情发生了、并对客体产生了影响,这时候,我们就应该拿出影响这一影响的办法来。

    举个例子:大楼的火警响了(火警鸣响这一事件发生),它产生的影响是让楼内的所有人员都听到了警报声,楼内的人纷纷拿出自己响应这一影响的方法来——普通职员们飞奔出大楼,而消防人员却向相反的方向跑,冲向最危险的火场。我们把这种套路称为“事件响应机制”,用于响应事件所造成的影响而采取的行动简称为“事件响应方法”。特别注意:员工逃跑和消防员冲向火场都是对警号鸣响这一事件的响应方法,而非事件所产生的影响。

    对了,还有个小问题:火警响了,我们为什么会跑呢?呵呵,答案很简单——因为我们时刻关心着警报会不会响这个事件。

             OK,非常感谢你能把上面的文字读完——初中语文老师的水平完全可以决定一个学生以后是不是能成为一名合格的程序员。

    .NET Framework 中事件的概念

             下面让我们再来看看C#中“事件”(Event)是什么,并且是如何与现实世界中的事件概念相对应。

    1.          MSDNevent关键字的解释:
    Events are used on classes and structs to notify objects of occurrences that may affect their state.
    事件被用在类和结构体上,用处是通知某些对象——这些对象的状态有可能被事件的发生所影响。

    2.          MSDNEvent的解释:
    An event is a way for a class to provide notifications when something of interest happens. 
    事件,是当某些被关注的事情发生时类提供通知的一种途径。

    3.          C# Spec中对Event成员的解释:
    An event is a member that enables an object or class to provide notifications. Clients can attach executable code for events by supplying event handlers.
    事件是一种类成员,它使得对象或类能够提供通知。客户端(被通知的对象/类)可以为事件附加上一些可执行代码来响应事件,这些可执行代码称为“事件处理器”(Event Handlers)。

    4.          我自己的解释:
    Events is a kind of member of class and structs, and it is a way for class and structs who own the events to notify objects who cares these events and provides the event handlers when these events fire.

             事件是类和结构体的一种成员。当事件发生时,“事件”是一种拥有此事件的类/结构体通知关心(或者称为“订阅”)这些事件、并提供事件处理器的对象的一种途径。

     

     

    干说没啥意思,下面我还是给出相应的代码,带领大家体验一下什么是事件、事件是怎么声明的、事件如何被其它类“订阅”、订阅了事件的类又是如何响应(处理)事件的。

     

    我们就以大楼火警为例,给出下面的代码:

    //=============水之真谛============
    //
    //        http://blog.csdn.net/FantasiaX
    //
    //=========上善若水,润物无声==========
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace EventSample
    {
        // 委托是事件的基础,是通知的发送方与接收方双方共同遵守的"约定"
        delegate void FireAlarmDelegate();

        // 大楼(类)
        class Building
        {
            // 声明事件:事件以委托为基础
            public event FireAlarmDelegate FireAlarmRing;
            
            //大楼失火,引发火警鸣响事件
            public void OnFire()
            {
                this.FireAlarmRing();
            }
        }

        // 员工(类)
        class Employee
        {
            // 这是员工对火警事件的响应,即员工的Event handler。注意与委托的匹配。
            public void RunAway()
            {
                Console.WriteLine("Running awary...");
            }
        }

        // 消防员(类)
        class Fireman
        {
            // 这是消防员对火警事件的响应,即消防员的Event handler。注意与委托的匹配。
            public void RushIntoFire()
            {
                Console.WriteLine("Fighting with fire...");
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Building sigma = new Building();
                Employee employee = new Employee();
                Fireman fireman = new Fireman();

                // 事件的影响者"订阅"事件,开始关心这个事件发生没发生
                sigma.FireAlarmRing+=new FireAlarmDelegate(employee.RunAway);
                sigma.FireAlarmRing += new FireAlarmDelegate(fireman.RushIntoFire);

                //由你来放火!
                Console.WriteLine("Please input 'FIRE' to fire the building...");
                string str = Console.ReadLine();
                if (str.ToUpper()=="FIRE")
                {
                    sigma. OnFire();
                }
            }
        }
    }
          上面的代码中提到:事件是基于委托的,委托不但是声明事件的基础,同时也是通知收发双方必需共同遵守的一个“约定”。OK,让我们改进一下上面的例子,进一步发挥事件的威力。

          设想这样一个情况:大楼一共是7层,每层的防火做的也不错,只要不是火特别大那么就没必要让所有人都撤离——哪层着火,哪层员工撤离。还有就是一个火警的级别问题:我们把火的大小分为三级——

    C级(小火):打火机级,我左边的兄弟比较喜欢抽烟,一般他点烟的时候我不跑。

    B级(中火):比较大了,要求所在楼层的人员撤离。

    A级(大火):一般女友发脾气都是这个级别,要求全楼人撤离。

    OK,让我们看看代码:

    //=============水之真谛============
    //
    //        http://blog.csdn.net/FantasiaX
    //
    //=========上善若水,润物无声==========
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace EventSample
    {
        // 事件参数类:记载着火的楼层和级别
        class FireEventArgs
        {
            public int floor;
            public char fireLevel;
        }

        // 委托是事件的基础,是通知的发送方与接收方共同遵守的"约定"
        delegate void FireAlarmDelegate(object sender, FireEventArgs e);

        // 大楼(类)
        class Building
        {
            // 声明事件:事件以委托为基础
            public event FireAlarmDelegate FireAlarmRing;

            //大楼失火,引发火警鸣响事件
            public void OnFire(int floor, char level)
            {
                FireEventArgs e = new FireEventArgs();
                e.floor = floor;
                e.fireLevel = level;
                this.FireAlarmRing(this, e);
            }
            public string buildingName;
        }

        // 员工(类)
        class Employee
        {
            public string workingPlace;
            public int workingFloor;
            // 这是员工对火警事件的响应,即员工的Event handler。注意与委托的匹配。
            public void RunAway(object sender, FireEventArgs e)
            {
                Building firePlace = (Building)sender;
                if (firePlace.buildingName == this.workingPlace && (e.fireLevel == 'A' || e.floor ==this.workingFloor))
                {
                    Console.WriteLine("Running awary...");
                }
            }
        }

        // 消防员(类)
        class Fireman
        {
            // 这是消防员对火警事件的响应,即消防员的Event handler。注意与委托的匹配。
            public void RushIntoFire(object sender, FireEventArgs e)
            {
                Console.WriteLine("Fighting with fire...");
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Building sigma = new Building();
                Employee employee = new Employee();
                Fireman fireman = new Fireman();

                sigma.buildingName = "Sigma";
                employee.workingPlace = "Sigma";
                employee.workingFloor = 1;

                // 事件的影响者"订阅"事件,开始关心这个事件发生没发生
                sigma.FireAlarmRing += new FireAlarmDelegate(employee.RunAway);
                sigma.FireAlarmRing += new FireAlarmDelegate(fireman.RushIntoFire);

                //由你来放火!
                Console.WriteLine("Please input 'FIRE' to fire the building...");
                string str = Console.ReadLine();
                if (str.ToUpper() == "FIRE")
                {
                    sigma.OnFire(7, 'C');
                }
            }
        }
    }
           我们仔细分析一下上面的代码:

    1.        较之第一个例子,本例中多了一个class FireEventArgs 类,这个类是专门用于传递“着火”这件事的参数的——请回过头去看“现实世界中的事件”一段——我们关心的主要是着火的地点和火的级别,因为在这个类中我们有两个成员变量。

    2.        接下来的委托也有所改变,由无参变成了两个参数——object sender, FireEventArgs e ,大家可能会问:为什么你写两个参数,而不用3个或者4个?唔……传统习惯就是这样,这个传统的开端应该可以追溯到VC的Win32时代吧——那时候,消息传递的参数就是一个lParam和一个wParam,分别承载着各种有用的信息——VB模仿它们,一个改名叫sender,一个改名叫e,然后C#又传承了VB,就有了你看到的样子。至于为什么第一个参数是object型,解释起来需要用到一些“多态”的知识,在这里我先不说了,我会在《深入浅出话多态》中以之为例。第二个参数使用了我们自己制造的类,这个类里带着两个有用的信息,上面也提到过了。

    3.        在Building类的OnFire方法函数中,进行了参数的传递——你完全可以理解成:Building类将这些参数(一个是this,也就是自己,另一个是e,承载着重要信息)发送给了接收者,也就是发送给了关心/订阅了这一事件的类。在我们这个例子中,订阅了Building类事件的对象分别是employee和fireman,他们会在事件发生的时候得到通知,并从传递给他们的事件参数中筛选自己关心的内容。这一筛选是由程序员完成的,在本例中,Employee对消息就做了筛选,而Fireman类不加筛选,见火就灭(我有点担心我左边的兄弟)。

    4.        注意:Building类有一个buildingName域,因为Building是事件的持有者,所以在事件激发的时候,它也是消息的发送者(sender),所以,这个buildingName域也会随着this被发送出去。如果你理解什么是多态,那么OK,你会明白为什么可以使用Building firePlace = (Building)sender;把这个buildingName读出来。

    5.        Employee类是本程序中最有意思的类。它不但提供了对Build类事件的影响,还会对事件进行智能筛选——只有当自己所工作的大厦警报鸣响,并且是自己所在楼层失火或火势足够大时才会撤离。请仔细分析public void RunAway(object sender,FireEventArgs e)方法。

    6.        与Employee类不同,Fireman类对事件是不加筛选的——你想啦,灭火可是消防员的职责,不论哪里着火,他们都会勇往直前!

    7.        进入主程序,代码相当清晰——的确是这样,基本类库的代码总是比较复杂(在本例中,基本类库是指Building、Employee、Fireman这几个类)。一般情况下,基本类库与主程序的开发者不是一个人,基本类库的源代码一般也不向主程序的开发者开放,而是以DLL(Assembly)文件的形式发放,所以实际开发中整个程序看起来是非常清晰的。

    8.                    sigma.FireAlarmRing += new FireAlarmDelegate(employee.RunAway);
                sigma.FireAlarmRing += new FireAlarmDelegate(fireman.RushIntoFire);
    通过这两句你能看出什么来?呵呵,因为事件是基于委托的,所以事件也是多播的!如果不明白什么是多播委托,请参见《深入浅出话委托》。

    9.        因为员工在1F工作,所以7层的C级火不会导致其撤离——只有消防员会响应。

     

     

    TO BE CONTINUE

     

     

    法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。若您需要转载,请务必注明文章出处为CSDN以保障网站的权益;请务必注明文章作者为刘铁猛,并向bladey@tom.com发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!

  • 相关阅读:
    bzoj4705: 棋盘游戏
    bzoj4709 [jsoi2011]柠檬
    51nod 1411 矩阵取数问题 V3
    51nod1258 序列求和V4
    51nod 1348 乘积之和
    PostgreSQL Replication之第九章 与pgpool一起工作(3)
    PostgreSQL Replication之第九章 与pgpool一起工作(2)
    PostgreSQL Replication之第九章 与pgpool一起工作(1)
    PostgreSQL Replication之第八章 与pgbouncer一起工作(5)
    PostgreSQL Replication之第八章 与pgbouncer一起工作(4)
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/2660224.html
Copyright © 2011-2022 走看看