++分析猫和老鼠示例
++++假如有一只猫叫Tom,有两只老鼠Jerry和Jack,Tom只要叫一声喵,我是Tom。 两只老鼠就会说,老猫来了,快跑。
++++分析上面的案例,有几个类,如何处理类与类之间的关系?
++++猫和老鼠
-- 1、至少存在两个类,Cat和Mouse
-- 2、在Cat类中存在一个方法,叫Shout。
-- 3、当Shout方法触发时,Mouse类应该执行某个方法,比如Run。
--显然猫和老鼠并不认识,不会主动通知老鼠我来了。 那么老鼠应该是监听猫的行为,当猫的行为触发了,老鼠自然就知道了。
++事件的引入
++++在Cat类中,不应该关联Mouse类。此时委托事件的方式就是最好的处理办法,下面我们创建一个Cat类,部分代码如下:
private string name;
public Cat(string name){
this.name = name;
}
public delegate void CatShoutEventHandler();
public event CatShoutEventHandler CatShout;
++事件的定义
++++事件是说在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。 事件的关键字是event。
public event CatShoutEventHandler CatShout;
这行代码的意思是声明事件CatShout,它的事件类型是委托CatShoutEventHandler。
正如上节课我们提到的,委托是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。
++++Shout部分代码
public void Shout(){
Console.WriteLine(“喵,我是{}!”, name);
if(CatShout != null){ //表示当执行Shout方法时,如果CatShout中有对象登记事件,则执行
CatShout();
}
}
调用事件与调用委托是一样的,这里需要注意CatShout()之所以不带任何参数,是因为CatShout的事件类型是委托CatShoutEventHandler(),而它本身是无参数,无返回值的。
++++Mouse部分代码:
private string name;
public Mouse(string name){
this.name = name;
}
public void Run(){
Console.WriteLine(“老猫来了,{0}快跑!”, name);
}
++++Main函数部分代码
Cat cat = new Cat(“Tom”);
Mouse m1 = new Mouse(“Jerry”);
Mouse m2 = new Mouse(“Jack”);
cat.CatShout += new Cat.CatShoutEventHandler(m1.Run);
cat.CatShout += new Cat.CatShoutEventHandler(m2.Run);
cat.Shout();
Console.Read();
代码分析:我们先来看看在我们声明委托的时候,这句代码的意义:
public delegate void CatShoutEventHandler();
这行代码里面我们用的修饰符是public,众所周知意味着公开的意思,任何外部存在的类都可以进行赋值(绑定委托时使用=号)。在某种程度上,委托破坏了对象的封装性。
++如何封装委托
++++你是猴子请来的逗逼吗?
为了保证不破坏对象的封装性,我们将public修改为private。(这特么简直就是在搞笑!)
因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,让外部帮我去做事情的。你把它声明为private了,客户端对它根本就不可见,那它还叫委托吗?
++++那如何封装委托呢?
我们刚接触面向对象的时候,介绍过字段一般推荐用private修饰,为了防止外界破坏类的内部结构,同时为了外界能访问到私有属性,我们引入属性这一概念。 即面向对象三大特性之一的封装性。
于是,Event出场了,它封装了委托类型的变量。 使得: 在类的内部,不管你声明它是public还是protected,它总是private的。 在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
++++为了验证这个问题,我们修改了Main函数部分代码:
Cat cat = new Cat(“Tom”);
Mouse m1 = new Mouse(“Jerry”);
Mouse m2 = new Mouse(“Jack”);
//这里我们修改了+=为=
cat.CatShout = new Cat.CatShoutEventHandler(m1.Run);
cat.CatShout += new Cat.CatShoutEventHandler(m2.Run);
cat.Shout();
Console.Read();
运行程序,会出现编译错误
!!!CS 0070事件Cat.CatShout只能出现在+=或-=的左边(从类型”Cat”中使用时除外)
编译错误很清晰的告诉我们除了在类的内部,我们不能用除了+=符号来绑定事件,当然也可以通过反编译去查看源码(后面高级课我们会介绍如何利用Refector反编译源代码)
++++事件其实没有什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
++总结
++++1、事件是一种特殊的委托的实例,或者说是受限制的委托,是委托一种特殊应用,在类的外部只能施加+=,-=操作符,二者本质上是一个东西。
++++2、事件只允许用add(+=),remove(-=)方法来操作,这导致了它不允许在类的外部被直接触发,只能在类的内部适合的时机触发。 委托可以在外部被触发,但是别这么用。 使用中,委托常用来表达回调,事件表达外发的接口。
++++3、事件不可以当作形参传递,但是委托可以。
++思考一个问题,如何在猫来的时候让老鼠知道是哪只猫来了呢?
++++这时候我们需要定义一个类,这个类继承自EventArgs。
++++EventArgs是包含事件数据的类的基类。换句话说见明知意,这个类的作用就是用来在事件触发时传递数据的。(当然也可以用别的方法实现后面会介绍)
++++新建类CatShoutEventArgs,代码如下:
class CatShoutEventArgs : EventArgs{
private string name;
public string Name{
set{ name = value; }
get{return name; }
}
}
++++修改Cat类部分代码:
-- 1、修改委托
public delegate void CatShoutEventHandler(object sender,CatShoutEventArgs args);
-- 2、修改Shout中部分代码:
CatShoutEventArgs args =new CatShoutEventArgs();
args.Name = this.name;
CatShout(this, args);
++++修改Mouse类部分代码:
public void Run(object sender,CatShoutEventArgs args){
Console.WriteLine(“老猫{0}来了,{1}快跑!”, args.Name, name);
}
++++运行程序,你会发现结果和之前显示的不一样了! 我们通过一个类去传递事件发生时想要传递的参数,对于客户端(Main)方法我们一句代码都没有动过!
我们修改了代码,客户端没有更改就改变了结果。换句话说,对于我们内部的类来说修改没有影响到别的类,这就是一定程度上的松耦合。
++猫和老鼠案例总结
++++通过猫和老鼠的案例,我们学习了事件的用法。 其实这个案例是典型的观察者设计模式,称为Observer设计模式:Observer设计模式是为了定义对象间的一种一对多的依赖关系(一只猫多个老鼠),以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更行。 Observer模式是一种松耦合的设计模式。
++Observer设计模式
++++观察者模式又叫 发布-订阅模式(Publish/Subscribe)
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主体对象在状态发生变化时,会通知所有的观察者对象,是他们能够自动更新自己。
++++观察者模式一般会牵扯至少两个角色
Subject或Publish,叫做具体主体或通知者,将所有有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
observer或subscribe类,具体观察者。
++++在猫与老鼠的案例中,猫是发布者,老鼠尾订阅者。 当猫的某个状态发生改变时(这里是方法触发时),订阅者即老鼠收到了相关通知,去改变自身的行为。
++Observer模式的特点
++++当一个对象的改变需要同时改变其他对象的时候,并且它不知道具体有多少对象有待改变时,应该采用观察者模式。
++++观察者模式所做的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
++Observer模式最终案例
++++玩手机的同学,代码如下:
class PlayObserver{
private string name;
private Subject sub;
public PlayObserver(string name,Subject sub){
this.name = name;
this.sub = sub;
}
//放下玩手机的首,在假装写代码
public void PlayPhoneToCode(){
Console.WriteLine(“{0} {1}放下手机,假装在写代码”, sub.SubjectState, this.name);
}
}
++++看视频的同学,代码如下:
class ViewObserver{
private string name;
private Subject sub;
public ViewObserver(string name,Subject sub){
this.name = name;
this.sub = sub;
}
//放下玩手机的手,在假装写代码
public void WatchVideoToCode(){
Console.WriteLine(“{0} {1}关掉视频,切换到写代码”, sub.SubjectState, this.name);
}
}
++++客户端,部分代码如下:
Boss ylzServ =new Boss();
PlayObserver lisi =new PlayObserver(“李四”, ylzServ); //玩手机的同学
ylzServ.Update += new EventHandler(lisi.PlayPhoneToCode); //监听
ylzServ.SubjectState = “钻钻来查班了!”; //老师来了
ylzServ.Notify(); //发出通知
Console.Read();
++事件委托说明
++++1、委托就是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为。
++++2、委托方法的使用可以像其他任何方法一样,具有参数和返回值。
++++3、委托可以看作是对函数的抽象,是函数的“类”,委托的实例将代表一个具体的函数。
++++4、一个委托可以搭载多个方法,所有方法被依次唤起,更重要的是,它可以使委托对象搭载的方法不需要属于同一个类。
++.Net Framework中的委托与事件
++++我们先搞懂.Net Framework的编码规范:
--委托类型的名称都应该以EventHandler结束。
--委托的原型定义: 有一个void返回值,并接受两个输入参数: 一个Object类型,一个EventArgs类型(或继承自EventArgs)。
--事件的命名为: 委托去掉EventHandler之后剩余的部分。
--继承自EventArgs的类型应该以EventArgs结尾。
++++再做一下说明:
--1、委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是Heater(热水器)。 回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
--2、EventArgs对象也包含了Observer所感兴趣的数据,在本例中是temperature。
++++上面描述不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。(比如说,如果我们不光想获得热水器的温度,还想再Observer段(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了)。
++++一个符号.Net Framework规范的示例:
using System;
using System.Collections.Generic;
using System.Text;
namespace Delegate{
//热水器
public class Heater{
private int temperature;
public string type =“VRunSoft 001”; //添加型号作为演示
public string area =“China Beijing”; //添加产地作为演示
//声明委托
public delegate void BoiledEventHandler(Objectsender,BoildEventArgse);
public event BoiledEventHandler Boiled; //声明事件
//定义BoiledEventArgs类,传递给Observer所感兴趣的信息
public class BoiledEventArgs : EventArgs{
public readonly int temperature;
public BoiledEventArgs(int temperature){
this.temperature = temperature;
}
}
//可以供继承自Heater的类重写,以便继承类拒绝其他对象对它的监视
protected virtual void OnBoiled(BoiledEventArgse){
if(Boiled != null){ //如果有对象注册
Boiled(this, e); //调用所有注册对象的方法
}
}
//烧水
public void BoilWater(){
for(int i = 0; i <= 100; i++){
temperature = i;
if(temperature > 95){
//建立BoiledEventArgs对象
BoiledEventArgse = new BoiledEventArgs(temperature);
OnBoiled(e); //调用OnBolied方法
}
}
}
}
//警报器
public class Alarm{
public void MakeAlert(Object sender, Heater.BoiledEventArgs e){
Heater heater = (Heater)sender; //这里是不是很熟悉呢?
//访问sender中的公共字段
Console.WriteLine(“Alarm: {0}-{1}:”, heater.area, heater.type);
Console.WriteLine(“Alarm:滴滴滴,水已经{0}度了:”, e.temperature);
Console.WriteLine();
}
}
//显示器
public class Diplay{
public static void ShowMsg(Object sender,Heater.BoiledEventArgs e){//静态方法
Heaterheater = (Heater)sender;
Console.WriteLine(“Display:{0}-{1}:”, heater.area, heater.type);
Console.WriteLine(“Display:水快烧开了,当前温度:{0}度。”, e.temperature);
Console.WriteLine();
}
}
class Program{
static void Main(){
Heater heater = new Heater();
Alarm alarm = new Alarm();
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法。
}
}
}
输出为: ....
++10、总结
++++在本文中我们首先通过一个GreetingPeople的小程序向大家介绍了委托的概念、委托用来做什么,随后又引出了事件,接着对委托与事件所产生的中间代码做了粗略的讲述。
++++在第二个稍微复杂点的热水器的范例中,我们简要介绍了Observer设计模式,并通过实现这个范例完成了该模式,随后讲述了.Net Framework中委托、事件的实现方法。
#立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/
++立钻哥哥推荐的拓展学习链接(Link_Url):
++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/
++++C#事件:https://blog.csdn.net/vrunsoftyanlz/article/details/78631267
++++C#委托:https://blog.csdn.net/vrunsoftyanlz/article/details/78631183
++++C#集合:https://blog.csdn.net/vrunsoftyanlz/article/details/78631175
++++C#泛型:https://blog.csdn.net/vrunsoftyanlz/article/details/78631141
++++C#接口:https://blog.csdn.net/vrunsoftyanlz/article/details/78631122
++++C#静态类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630979
++++C#中System.String类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630945
++++C#数据类型:https://blog.csdn.net/vrunsoftyanlz/article/details/78630913
++++Unity3D默认的快捷键:https://blog.csdn.net/vrunsoftyanlz/article/details/78630838
++++游戏相关缩写:https://blog.csdn.net/vrunsoftyanlz/article/details/78630687
++++Unity引擎基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78881685
++++Unity面向组件开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78881752
++++Unity物理系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78881879
++++Unity2D平台开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78882034
++++UGUI基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78884693
++++UGUI进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78884882
++++UGUI综合:https://blog.csdn.net/vrunsoftyanlz/article/details/78885013
++++Unity动画系统基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78886068
++++Unity动画系统进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78886198
++++Navigation导航系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78886281
++++Unity特效渲染:https://blog.csdn.net/vrunsoftyanlz/article/details/78886403
++++Unity数据存储:https://blog.csdn.net/vrunsoftyanlz/article/details/79251273
++++Unity中Sqlite数据库:https://blog.csdn.net/vrunsoftyanlz/article/details/79254162
++++WWW类和协程:https://blog.csdn.net/vrunsoftyanlz/article/details/79254559
++++Unity网络:https://blog.csdn.net/vrunsoftyanlz/article/details/79254902
++++设计模式简单整理:https://blog.csdn.net/vrunsoftyanlz/article/details/79839641
++++U3D小项目参考:https://blog.csdn.net/vrunsoftyanlz/article/details/80141811
++++UML类图:https://blog.csdn.net/vrunsoftyanlz/article/details/80289461
++++Unity知识点0001:https://blog.csdn.net/vrunsoftyanlz/article/details/80302012
++++U3D_Shader编程(第一篇:快速入门篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372071
++++U3D_Shader编程(第二篇:基础夯实篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372628
++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/
--_--VRunSoft:lovezuanzuan--_--