首页 > 代码库 > 委托(二):使用事件来对委托变量进行封装

委托(二):使用事件来对委托变量进行封装



          接上篇:委托(一):委托与方法 


         

             在以前学习设计模式的时候,我们经常看到这样的代码:


        

                




          可以这样说,委托和事件经常一起使用,感觉挺麻烦的,而且觉得委托已经挺好了,为什么还要加入事件呢?搞得挺复杂的。ok,下面来通过个小例子来说明为什么要使用事件。


           例子中各个类结构如图(我们将上篇博文的实例进行了改进,把方法分散到不同的类中,尽量模拟实际调用时的情况):



         


 

    首先是最简单的GreetingMethod类代码:



        

namespace 委托和事件
{
   public  class GreetingMethod
    {
        /// <summary>
        /// 英语的说早上好的方法
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("good,morning!" + name);

        }


        /// <summary>
        /// 汉语的问好方法
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好,宝贝儿~~~" + name);

        }


    }
}




         在GreetingMethod类中,我们将具体的Greeting方法放在了这个类中,这里先不考虑以后的变化因素。



          接着是GreetingManager类,在这个类里面,我们将原先的GreetPeople方法搬移到这个类里面:




   

namespace 委托和事件
{
    //委托的定义
    //1,委托:委托出现的位置和string相同,所以GreetingDelegate应该也是个类型,但是委托的声明方式和类完全不同。
    //2,委托在编译的时候确实会被编译成类,因为delegate是一个类,所以在任何可以生命类的地方都可以声明委托。
    public delegate void GreetingDelegate(string name);



    /// <summary>
    /// 管理方法调用的类
    /// </summary>
    public class GreetingManager
    {
        #region 在类内部定义委托变量

            ////第一次改进:在GreetingManager类的内部声明delegate1变量
            //public GreetingDelegate delegate1;
        
        #endregion

        #region 使用event

            public event GreetingDelegate MakeGreet;

        #endregion


       

        //使用了委托之后,对GreetPeople方法进行的改进

        #region 将委托作为方法的参数传递到函数里
        ///// <summary>
        ///// 调用问候方法
        ///// </summary>
        ///// <param name="name">姓名</param>
        ///// <param name="MakeGreeting">具体调用的方法名称</param>
        //public void GreetPeople(string name, GreetingDelegate MakeGreeting)
        //{
        //    MakeGreeting(name);

        //} 
        #endregion



        #region 在方法内部直接调用成员变量delegate1

            //public void GreetPeople(string name)
            //{
            //    if (delegate1 != null) {  //如果委托变量已被赋值
            //        delegate1(name);  //那么直接调用
            //    }
            //}
        #endregion


        #region 使用event

            public void GreetPeople(string name)
            {
                MakeGreet(name);
            
            }


        /***
         * MakeGreet事件的声明与之前的委托变量delegate1的声明唯一的区别就是多了一个event关键字。
         * 
         * so..........
         * 
         * 1,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
         * 
         * 2,event对委托变量进行了封装,类似于以前的get,set字段
         * 
         * 
         * 
         * ***/
        #endregion


    }
}


        接着是客户端的调用:


  

namespace 委托和事件
{
    class Program
    {
        static void Main(string[] args)
        {

            #region 将GreetingManager放入其他类中之后客户端调用

                //GreetingManager manager = new GreetingManager();
                //manager.GreetPeople("水田如雅", GreetingMethod.EnglishGreeting);
                //manager.GreetPeople("vac", GreetingMethod.ChineseGreeting); 

            #endregion


            #region 多个方法绑定同一个委托变量

                //GreetingManager manager = new GreetingManager();

                //GreetingDelegate delegate1;
                //delegate1 = GreetingMethod.EnglishGreeting;
                //delegate1 += GreetingMethod.ChineseGreeting;

                //manager.GreetPeople("水田如雅", delegate1);


            #endregion


            #region 在GreetingManager类内部声明委托后客户端调用示例

                //GreetingManager gm = new GreetingManager();
                //gm.delegate1 = GreetingMethod.EnglishGreeting;
                //gm.delegate1 += GreetingMethod.ChineseGreeting;

                //gm.GreetPeople("水田如雅",gm.delegate1);



            //PS:尽管这样做没有任何问题,但是这条语句很奇怪,在调用gm.GreetPeople("水田如雅",gm.delegate1);方法的时候,再次传递了gm的delegate1字段;但是delegate1本身就在gm中。所以这里需要再次改进。

            #endregion



            #region 改进GreetingManager类的GreetPeople方法之后的调用

                //GreetingManager gm = new GreetingManager();
                //gm.delegate1 = GreetingMethod.EnglishGreeting;
                //gm.delegate1 += GreetingMethod.ChineseGreeting;

                //gm.GreetPeople("水田如雅");//这次调用的时候,直接传入name,不再需要传入delegate了


            #endregion



            #region 继续思考,thinking............

            /*
             * 虽然取得了想要的效果,但是还是存在问题。
             * 1,在这里,delegate1和平时用的string类型的变量并没有什么分别,并不是所有的成员变量都应该声明为public,按照信息隐蔽的原则,我们应当尽量封装数据。
             * 2,但是如果将delegate1声明为private,那么,我们如何为它赋值呢?如果不能赋值,如何注册方法呢?
             * 3,delegate1声明为了public,则可以对他进行任意修改,破坏了封装性。
             * 
             * 
             * 4,解决上述问题的方法,使用event...............
             
             
             */
            #endregion


            #region 使用了event之后的调用

                //GreetingManager gm = new GreetingManager();

                ////注意使用时,第一次也是“+=”,不同于直接使用委托
                //gm.MakeGreet += GreetingMethod.EnglishGreeting;
                //gm.MakeGreet += GreetingMethod.ChineseGreeting;

                //gm.GreetPeople("水田如雅");


            #endregion




        }
    }
}


       接下来来描述下我们对这个小Demo的改进过程:

       

            使用委托,我们主要是为了达到【方便】【安全】的调用方法。


             为了方便的调用,我们先是将在GreetingManager类的内部声明delegate1变量,之后发现了这样做,




         在调用上述方法的时候,又要在本类的方法中将本类的成员变量赋值给本类的方法,这样做很奇怪,为什么不直接就在这个类的方法内部直接调用它的成员变量呢,于是我们就去掉了上述方法中第二个参数,但是这样还是有问题,就是类成员的访问问题。


         原则上来说,我们应当尽量降低类成员的访问权限,来达到封装效果,但是现在,我们直接就在客户端赋值了本该封装的成员变量。



        为了解决这个问题,我们回想下,以前我们是如何隐藏字段的:


            



            yes,我们使用property来对内部的的private成员变量进行访问控制。



                  但是对于delete的访问控制,似乎更简单,我们在声明委托的时候,顺便在前面加上个event就ok了:



      

     这样,就变相完成了访问控制的作用。




    小结:


      总结下引入event的好处:


               1,event封装了委托类型的变量,相当于为委托类型量身定制的属性(property)。

 

              2,使用事件不仅能获得比委托更好地方封装性,还能限制含有事件的类型的能力。



     

          

     

        

委托(二):使用事件来对委托变量进行封装