• 委托、事件、Observer观察者模式的使用解析一


    一、前言

    委托、事件得理论我就不解释了,不会的时候觉得很难,会了发现挺简单的,回头想想其实在JavaScript中常常用到,譬如:setTimeout()就是典型的委托。

    二、传统编码方式

    传统的调用方式如下,如果新加语言方法需要修改SayHello方法,增加case很不方便扩展

    /// <summary>
    /// 普通调用方式
    /// </summary>
    public class TestOld
    {
        public void English(string name)
        {
            Console.WriteLine("hello~" + name);
        }
        public void China(string name)
        {
            Console.WriteLine("你好~" + name);
        }
        public void SayHello(string name, int type)
        {
            switch (type)
            {
                case 1: English(name); break;
                case 2: China(name); break;
                default: break;
            }
        }
        public void TestInvoke()
        {
            string name = "Tom";
            SayHello(name, 1);
        }
    }

    三、改为委托方式

    改进为委托的方式之后会发现我们不再需要修改SayHello方法来适应扩展了,而是由调用方来实现不同的方法来完成自己的需求

    /// <summary>
    /// 委托调用方式
    /// </summary>
    public class TestDelegate
    {
        //委托:可以理解为定义一个类或者是数据类型以供变量指明(定义好函数的返回值和参数等约定信息)
        public delegate void SayDelegate(string name);
        public void English(string name)
        {
            Console.WriteLine("hello~" + name);
        }
        public void China(string name)
        {
            Console.WriteLine("你好~" + name);
        }
        public void SayHello(string name, SayDelegate sayDelegate)
        {
            sayDelegate(name);
        }
        public void TestInvoke()
        {
            string name = "Tom";
            string nameChina = "小台";
            /*
                * 委托单独调用方式
                * 将方法当作一个参数一样传递到方法内部
                */
            SayHello(name, English);
            SayHello(nameChina, China);
            /*
                * 委托多重绑定调用方式
                * 可以将多个方法绑定到同一个委托变量身上,再调用的时候就会依次调用所有绑定的方法
                * 注意:
                * 初始化的时候需要先使用=进行赋值
                * 递增的时候需要使用+=进行操作
                * 去除的时候需要使用-=进行操作
                */
            SayDelegate sayDelegate;    //声明委托变量
            sayDelegate = English;      //先给委托变量赋一个方法(第1个类似于初始化要用=等号)
            sayDelegate += China;       //再给委托变量赋一个方法(第2个类似于递增要用+=加等号)
            SayHello(name, sayDelegate);//调用的时候会先调用English方法,再调用China方法
    
            sayDelegate = new SayDelegate(English); //可以使用委托默认构造实例化
            sayDelegate += China;                   //递增一个中文方法
            sayDelegate -= English;                 //不需要英文方法,就可以使用-=来剔除掉
            SayHello(nameChina, sayDelegate);
        }
    }

    总结一下:

    1、委托就是类似于类或者数据类型一样的作用,在声明的时候约定这个类型的返回值和参数信息,方法调用的时候可以当做参数一样传递,方便了程序的扩展性;

    2、可以同时绑定多个方法,在调用的时候会按照注册顺序依次执行,注册第1个方法使用=等号赋值,注册第2个及之后方法使用+=加等号赋值,取消已注册的方法使用-=减等号进行赋值;

    四、改为事件方式

    在正常的应用环境下多数情况委托方法SayHello()在一个类中,扩展方法English()、China()在另一个类中,那我们可以把代码做如下改变

    /// <summary>
    /// 事件处理类
    /// </summary>
    public class TestEventHandler
    {
        public delegate void SayDelegate(string name);
        public void SayHello(string name, SayDelegate sayDelegate)
        {
            sayDelegate(name);
        }
    }
    /// <summary>
    /// 测试事件类
    /// </summary>
    public class TestEvent
    {
        public void English(string name)
        {
            Console.WriteLine("hello~" + name);
        }
        public void China(string name)
        {
            Console.WriteLine("你好~" + name);
        }
        public void TestInvoke()
        {
            string name = "Tom";
            TestEventHandler eventHandler = new TestEventHandler();
            eventHandler.SayHello(name, English);
            eventHandler.SayHello(name, China);
        }
    }

    如果只是调用English()或者China()其中某一个方法,那这样的确可以,但很多需求可能需要同时调用两个以上函数,那这样写的话就多写了一句eventHandler.SayHello()的代码,我们可以继续做如下改变

    public class TestEventHandler
    {
        public delegate void SayDelegate(string name);
        public SayDelegate SayDelegateHandler;          //声明一个委托变量
        //方法一
        public void SayHello(string name, SayDelegate sayDelegate)
        {
            sayDelegate(name);
        }
        //方法二
        public void SayHello(string name)
        {
            if (SayDelegateHandler != null)
            {
                SayDelegateHandler(name);
            }
        }
    }
    public class TestEvent
    {
        public void English(string name)
        {
            Console.WriteLine("hello~" + name);
        }
        public void China(string name)
        {
            Console.WriteLine("你好~" + name);
        }
        public void TestInvoke()
        {
            string name = "Tom";
            TestEventHandler eventHandler = new TestEventHandler();
            //把需要调用的方法全部注册到委托变量中
            eventHandler.SayDelegateHandler = English;
            eventHandler.SayDelegateHandler += China;
            //方法一(eventHandler要调用自己的方法还要传递自己的参数很别扭,所以要修改为方法二)
            eventHandler.SayHello(name, eventHandler.SayDelegateHandler);
            //方法二
            eventHandler.SayHello(name);
        }
    }

    看样子这样是可以了,可是仔细看方法一中还需要调用自己实在不合适,所以改为方法内部调用的方法二,

    1、前面方法注册的时候还是要第1个是=等号之后是+=加等号也很别扭以为内他们完成的功能都一样却使用不同的运算符,

    2、在加上“封装、继承、多态”的概念类中的属性要封装,而这个SayDelegate类型的属性和一个string类型的属性并没什么两样所以我们需要将这些属性封装,

    那要完成以上两个要求我们就要引入事件了,它既可以完成对委托的封装,又可以完成注册方法时的运算符不统一的问题,其实它也是只完成这些简单的功能,核心还是一个委托;

    public class TestEventHandler
    {
        public delegate void SayDelegate(string name);
        public event SayDelegate SayEventHandler;       //声明事件变量,其实可以理解为就是一个委托类型的变量
        public void SayHello(string name)
        {
            if (SayEventHandler != null)
            {
                SayEventHandler(name);
            }
        }
    }
    public class TestEvent
    {
        public void English(string name)
        {
            Console.WriteLine("hello~" + name);
        }
        public void China(string name)
        {
            Console.WriteLine("你好~" + name);
        }
        public void TestInvoke()
        {
            string name = "Tom";
            TestEventHandler eventHandler = new TestEventHandler();
            eventHandler.SayEventHandler += English;    //注册方法时,无论第1还是第2都是使用+=加等号来操作
            eventHandler.SayEventHandler += China;      
            eventHandler.SayHello(name);
            eventHandler.SayEventHandler -= English;    //取消已注册的方法时,依然是使用-=减等号来操作
            eventHandler.SayHello("小台");
        }
    }

    前面使用委托对象的时候反编译之后是一个对外公布的变量

    后面我们改成事件对象后反编译之后是两个封装之后的方法

    (对生成的dll或者exe进行反编译,推荐使用Red Gate公司的.Net Reflector生成代码完整基本和源码没有什么差别)

    事件应该有事件发布者来触发,而不应该由客户端(客户程序)来触发。

    eventHandler.SayDelegateHandler();  //编译通过,委托可以在外部执行,违背了由监视对象自己触发的原则
    eventHandler.SayEventHandler();     //编译错误,事件无法在外部被执行,只能由监视对象自己执行

     所以,设计程序时应该使用委托和事件关联使用。 

     此例子虽为自己原创,但也是在看了别人的讲解之后,自己练习时撰写的,附录我参考的文章地址:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx

  • 相关阅读:
    webpack4 css modules
    CSS多种方式实现底部对齐
    十大Web开发趋势
    CSS float相关介绍
    Web 堆栈选择指南:JAMStack vs MEAN vs LAMP
    a标签调用js函数写法总结
    如何给网页划分结构?
    九大高效的前端测试工具与框架
    移动端手机验证码四格、六格的input实现
    在modelarts上部署backend为TensorFlow的keras模型
  • 原文地址:https://www.cnblogs.com/taiyonghai/p/6544046.html
Copyright © 2020-2023  润新知