第九节:委托和事件(1)(委托的发展历史、插件式编程、多播委托)
一. 委托发展史和基本用法
说起委托,每个人可能都会对他有不同的理解,结合实战中委托的使用,我对其理解是:委托和类一样,是用户的一个自定义类型,委托可以有参数、有返回值,委托的关键字是delegate,委托是方法的抽象,有了委托的存在,使得方法可以作为参数传递给另一个方法,同时调用委托的时候,委托所包含的所有方法都会被实现。
委托的写法由繁琐→简洁,经历了好多过程,大致概括为:new实例化传递方法→直接等于方法名→new实例化中传递匿名方法→省略匿名方法关键字→可以去掉大括号和分号 等等。
代码如下:
1 public class MyDelegate 2 { 3 //1. 委托的声明 4 public delegate void NoReturnNoPara(); 5 public delegate int WithReturnNoPara(); 6 public delegate void NoReturnWithPara(int id, string name); 7 public delegate MyDelegate WithReturnWithPara(DateTime time); 8 9 10 //2. 委托的使用(在show方法中调用) 11 public void Show() 12 { 13 //以“有参无返回值委托”为例,介绍委托的各种用法 14 //2.1 用法一 15 { 16 NoReturnWithPara methord = new NoReturnWithPara(this.Test1); 17 methord.Invoke(1, "唐马儒1"); 18 } 19 //2.2 用法二 20 { 21 NoReturnWithPara methord = this.Test1; 22 methord.Invoke(2, "唐马儒2"); 23 } 24 //2.3 用法三 DotNet 2.0 时代 25 { 26 NoReturnWithPara methord = new NoReturnWithPara 27 ( 28 delegate(int id, string name) 29 { 30 Console.WriteLine("{0} {1}", id, name); 31 } 32 ); 33 methord.Invoke(3, "唐马儒3"); 34 } 35 //2.4 用法四 DotNet 3.0 时代 36 { 37 NoReturnWithPara methord = new NoReturnWithPara 38 ( 39 (int id, string name) => 40 { 41 Console.WriteLine("{0} {1}", id, name); 42 } 43 ); 44 methord.Invoke(4, "唐马儒4"); 45 } 46 //2.5 用法五 委托约束 47 { 48 NoReturnWithPara methord = new NoReturnWithPara 49 ( 50 (id,name) => 51 { 52 Console.WriteLine("{0} {1}", id, name); 53 } 54 ); 55 methord.Invoke(5, "唐马儒5"); 56 } 57 //2.6 用法六 (如果方法体只有一行,可以去掉大括号和分号) 58 { 59 NoReturnWithPara methord = new NoReturnWithPara((id, name) => Console.WriteLine("{0} {1}", id, name)); 60 methord.Invoke(6, "唐马儒6"); 61 } 62 //2.7 用法七 63 { 64 NoReturnWithPara methord = (id, name) => Console.WriteLine("{0} {1}", id, name); 65 methord.Invoke(7, "唐马儒7"); 66 methord(7, "唐马儒7"); 67 } 68 //2.8 用法八 69 { 70 //Func<int, bool> methord = (x) => 71 // { 72 // return x > 6; 73 // }; 74 //等价于(原理,当只有一个参数的时候,可以省略参数的小括号,当方法体只有一行的时候,可以省略大括号和分号,即lambda形式) 75 Func<int, bool> methord = x => x > 6; 76 Console.WriteLine(methord.Invoke(8)); 77 } 78 79 } 80 private void Test1(int id, string name) 81 { 82 Console.WriteLine("{0} {1}", id, name); 83 } 84 85 private void Test2() 86 { 87 Console.WriteLine("DoNothing"); 88 } 89 90 private void Test3() 91 { 92 Console.WriteLine("DoNothing"); 93 } 94 }
二. 解耦、插件式编程
1. 背景
有一个CarFactory工厂类,可以建造自然吸气发动机、涡轮增压发动机、电动发动机,至于要建造哪个发动机,有多种处理方案。
方案(一):通过传递不同的参数来建造不同的发动机
原理:传递一个参数,根据参数类型来执行不同的逻辑
缺点:如果增加新的发动机类型,需要修改BuildEngine方法中的内部逻辑,不符合开闭原则。
代码如下:
1 /// <summary> 2 /// 这里建一个Car工厂,用来建造三种不同类型的发动机 3 /// </summary> 4 public class CarFactory 5 { 6 /// <summary> 7 /// 方案一:通过传递不同的参数来建造不同的发动机 8 /// 原理:传递一个参数,根据参数类型来执行不同的逻辑 9 /// 缺点:如果增加新的发动机类型,需要修改BuildEngine方法中的内部逻辑,不符合开闭原则 10 /// </summary> 11 /// <param name="type">参数类型</param> 12 public static void BuildEngine(EngineType type) 13 { 14 if (type == EngineType.NaturalInspiration) 15 { 16 Console.WriteLine("建造自然吸气发动机"); 17 } 18 else if (type == EngineType.Turbo) 19 { 20 Console.WriteLine("建造涡轮增压发动机"); 21 } 22 else if (type == EngineType.Electric) 23 { 24 Console.WriteLine("建造电动发动机"); 25 } 26 } 27 28 /// <summary> 29 /// 发动机类型的枚举类 30 /// </summary> 31 public enum EngineType 32 { 33 NaturalInspiration = 0, //自然吸气 34 Turbo = 1, //涡轮增压 35 Electric = 2 //电动 36 } 37 }
调用:
1 //1.传统的方式,通过参数类型来区分 2 Console.WriteLine("--------------------------1.传统的方式,通过参数类型来区分------------------------------------"); 3 CarFactory.BuildEngine(EngineType.NaturalInspiration); 4 CarFactory.BuildEngine(EngineType.Turbo); 5 CarFactory.BuildEngine(EngineType.Electric);
结果:
方案(二):通过传递委托来建造不同的发动机
原理:传递一个逻辑给我,我去执行
优点:如果增加新的发动机,只需要单独新增对应建造发动机的方法即可,不需要改变BuildEngine2的内部逻辑,符合开闭原则
代码如下:
1 public class CarFactory 2 { 3 /// <summary> 4 /// 方案二:通过传递委托来建造不同的发动机 5 /// 原理:传递一个逻辑给我,我去执行 6 /// 优点:如果增加新的发动机,只需要单独新增对应建造发动机的方法即可,不需要改变BuildEngine2的内部逻辑,符合开闭原则 7 /// </summary> 8 public static void BuildEngine2(BuildEngineDel be) 9 { 10 be.Invoke(); 11 } 12 //声明一个无参数的委托 13 public delegate void BuildEngineDel(); 14 //下面三个是建造不同发动机的方法 15 public static void BuildNatural() 16 { 17 Console.WriteLine("建造自然吸气发动机"); 18 } 19 public static void BuildTurbo() 20 { 21 Console.WriteLine("建造涡轮增压发动机"); 22 } 23 public static void BulidElectric() 24 { 25 Console.WriteLine("建造电动发动机"); 26 } 27 28 } 29 }
调用:
1 //2.将委托当做参数,进行解耦,实现插件式编程(案例一) 2 Console.WriteLine("--------------------------2.将委托当做参数,进行解耦,实现插件式编程(案例一)------------------------------------"); 3 //将方法赋给委托,将委托当做参数传递给新方法 4 CarFactory.BuildEngineDel be1 = CarFactory.BuildNatural; 5 CarFactory.BuildEngine2(be1); 6 CarFactory.BuildEngineDel be2 = CarFactory.BuildTurbo; 7 CarFactory.BuildEngine2(be2); 8 CarFactory.BuildEngineDel be3 = CarFactory.BulidElectric; 9 CarFactory.BuildEngine2(be3);
结果:
2. 背景
现在有一个类,该类要实现对一个int类型的数组中的每个数进行加倍、平方、立方,并输出
传统解决方案一:在该类中声明多个方法,分别是加倍、平方、立方的方法
传统解决方案二:在该类中声明一个万能方法,通过传递不同的参数类型来区分是执行加倍还是平方或者立方操作
方案三:声明一个万能方法,传递一个委托进来,相当于传递了一个业务逻辑进行,在该方法里只需要执行即可
代码如下:
1 public class Calculator 2 { 3 //解决方案三:声明一个万能方法,传递一个委托进来,相当于传递了一个业务逻辑进行,在该方法里只需要执行即可 4 /// <summary> 5 /// 万能方法 6 /// </summary> 7 /// <param name="arrs">int类型的数组 </param> 8 /// <param name="mydel">系统自带的委托(也可以自定义委托),<int,int>代表:参数和返回值均为int类型</param> 9 public static void MySpecMethord(int[] arrs, Func<int,int> mydel) 10 { 11 for (int i = 0; i < arrs.Length; i++) 12 { 13 arrs[i] = mydel(arrs[i]); 14 //arrs[i] = mydel.Invoke(arrs[i]); //等价于上面那句 15 Console.WriteLine(arrs[i]); 16 } 17 } 18 19 }
三种调用形式:
声明Func委托把方法赋值给它、直接把方法名传递进去、传递lambda方法。
1 Console.WriteLine("--------------------------3.将委托当做参数,进行解耦,实现插件式编程(案例二)------------------------------------"); 2 int[] arr = { 1, 2, 3, 4, 5 }; 3 //调用形式一: 4 Console.WriteLine("--------------------------调用形式一------------------------------------"); 5 Func<int, int> mydel = t1; 6 Calculator.MySpecMethord(arr, mydel); 7 //调用形式二: 8 Console.WriteLine("--------------------------调用形式二------------------------------------"); 9 Calculator.MySpecMethord(arr, t2); 10 //调用形式三: 11 Console.WriteLine("--------------------------调用形式三------------------------------------"); 12 Calculator.MySpecMethord(arr, x => x * 2); 13 /// <summary> 14 /// 加倍方法 15 /// </summary> 16 /// <param name="x"></param> 17 /// <returns></returns> 18 public static int t1(int x) 19 { 20 return x * 2; 21 } 22 /// <summary> 23 /// 乘方方法 24 /// </summary> 25 /// <param name="x"></param> 26 /// <returns></returns> 27 public static int t2(int x) 28 { 29 return x * x; 30 } 31 /// <summary> 32 /// 立方方法 33 /// </summary> 34 /// <param name="x"></param> 35 /// <returns></returns> 36 public static int t3(int x) 37 { 38 return x * x * x; 39 }
结果:
三. 多播委托
1. 含义:
所有的委托的实例都有多播的功能,自定义委托和内置委托都有,可以通过+=和-=给委托增加和删掉不同的方法。
下面的代码中分别介绍四个不同类别的方法赋值给普通委托和多播委托的形式。
1 public class myMultiDelegate 2 { 3 //自定义一个没有参数没有返回值的委托 4 public delegate void NoReturnNoPara(); 5 6 /// <summary> 7 /// 测试委托常规用法 8 /// </summary> 9 public void show1() 10 { 11 Student student = new Student(); 12 //委托的普通用法,分别调用当前类的实例方法、当前类的静态方法、其他类的实例方法、其他类的静态方法 13 //声明4个委托 14 NoReturnNoPara n1 = this.DoNothing; //调用当前类的实例方法 15 NoReturnNoPara n2 = myMultiDelegate.DoNothingStatic; //调用当前类的静态方法 16 NoReturnNoPara n3 = student.Study; 17 NoReturnNoPara n4 = Student.StudyAdvanced; 18 19 //执行 20 n1.Invoke(); 21 n2.Invoke(); 22 n3.Invoke(); 23 n4.Invoke(); 24 } 25 /// <summary> 26 /// 测试多播委托的用法 27 /// </summary> 28 public void show2() 29 { 30 //所有的委托的实例都有多播的功能,自定义委托和内置委托都有 31 NoReturnNoPara n1 = this.DoNothing; 32 //+=就是把多个方法按顺序排成列表,invoke时按顺序执行 33 n1 += myMultiDelegate.DoNothingStatic; 34 n1 += Student.StudyAdvanced; 35 n1 += this.DoNothing; 36 n1 += this.DoNothing; 37 n1 += this.DoNothing; 38 n1 += this.DoNothing; 39 //-=就是从这个实例上,从后往前挨个匹配,找到第一个完全吻合的移除掉,且只移除一个,找不到不异常 40 //注意,对于委托,+= 和 -= 对null是不会报错的 41 n1 -= this.DoNothing; 42 n1.Invoke(); 43 } 44 45 46 #region 两个供委托调用的测试方法 47 private void DoNothing() 48 { 49 Console.WriteLine("当前类中的实例方法"); 50 } 51 private static void DoNothingStatic() 52 { 53 Console.WriteLine("当前类中的静态方法"); 54 } 55 #endregion 56 }
2. 多播委托的实际使用场景。
实际注册业务场景:先查询账号是否存在(如果不存在)→向表1中插入信息→向表2中插入信息。。。。,随着业务新需求的变更,注册时需要向表3中插入数据, 过了一段时间,需求又变了,不需要向表2中添加数据了。
下述代码是一个注册流程的类。
1 public class RegisterUtils 2 { 3 //传统的解决方案,在一个方法里书写各种业务。 4 //缺点:后期需求变更了,当需要插入更多数据的时候,只能修改该方法内部逻辑,不符合开闭原则 5 6 7 //下面介绍利用多播委托进行解耦插件式开发 8 public delegate void myRegisterDelegate(string userName, string userPwd); 9 10 public static void myRegister(myRegisterDelegate mrd, string userName, string userPwd) 11 { 12 //对密码进行Md5加密后在进行后续操作 13 string md5userPwd = userPwd + "MD5"; //模拟Md5 14 mrd.Invoke(userName, md5userPwd); 15 } 16 /// <summary> 17 /// 查询方法 18 /// </summary> 19 public static void checkIsRegister(string userName, string userPwd) 20 { 21 Console.WriteLine("查询成功"); 22 } 23 /// <summary> 24 /// 向表1中插入数据 25 /// </summary> 26 public static void writeTable1(string userName, string userPwd) 27 { 28 Console.WriteLine("向表1中插入数据{0},{1}",userName,userPwd); 29 } 30 /// <summary> 31 /// 向表2中插入数据 32 /// </summary> 33 public static void writeTable2(string userName, string userPwd) 34 { 35 Console.WriteLine("向表2中插入数据{0},{1}", userName, userPwd); 36 } 37 /// <summary> 38 /// 向表3中插入数据 39 /// </summary> 40 public static void writeTable3(string userName, string userPwd) 41 { 42 Console.WriteLine("向表3中插入数据{0},{1}", userName, userPwd); 43 } 44 }
调用
1 Console.WriteLine("--------------------------3.多播委托的案例(在注册流程中) ------------------------------------"); 2 RegisterUtils.myRegisterDelegate mrd = RegisterUtils.checkIsRegister; 3 mrd += RegisterUtils.writeTable1; 4 mrd += RegisterUtils.writeTable2; 5 //需求增加了,向表3中添加数据 6 mrd += RegisterUtils.writeTable3; 7 //需求变更了,删除向表2中添加数据 8 mrd -= RegisterUtils.writeTable2; 9 RegisterUtils.myRegister(mrd, "maru", "123");
结果:
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。