AI智能
改变未来

C#学习(三)——委托与事件

在面试中,委托与事件几乎是必问的。如果面试官和面试者互不相识,面试官不问委托,严重怀疑这家公司的技术水平,这个可以说是C#入门与掌握的分水岭之一,非常重要。对委托和事件做一个全面的整理和归纳。这里重在理解,只有理解了才能真正掌握它。

  1. 为什么C#会有委托事件?
    解决观察者模式的缺陷。这里又引申出一个问题,什么是观察者模式,观察者模式的缺陷又是什么?在回答这个问题前,我们其实已经知道一个答案,那就是先有设计模式中的观察者模式,再有委托事件这一技术。它有一个前后顺序,如果你要彻底学会委托事件,那么就要先学会观察者模式,还要了解观察者模式的缺陷。

2.观察者模式的缺陷:
观察者模式是对主题对象和观察者对象进行解耦,使双方都依赖与抽象,而不是依赖于对方的具体对象,使双方的变化都不会影响到对方的具体对象。当多个对象需要根据一个对象的状态发生相应的改变或操作时,可使用观察者模式。

这里我写一个小demo,非常简洁。还是猫和老鼠,猫是主题对象,人和老鼠是观察者。猫叫的时候,人和老鼠要执行相应操作。

使用观察者模式实现这一功能:

using System.Collections;using System.Collections.Generic;using UnityEngine;public class ObserverCatDemo : MonoBehaviour{void Start(){CatSubject cat = new CatSubject();Mouse mouse = new Mouse();Person person = new Person();cat.Add(mouse);cat.Add(person);cat.Call();}}public abstract class Subjectss{public abstract void Add(Observerss observer);public abstract void Remove(Observerss observer);public abstract void Call();}public abstract class Observerss{public abstract void Update();}public class CatSubject : Subjectss{public IList<Observerss> Observers = new List<Observerss>();public override void Add(Observerss observer){Observers.Add(observer);}public override void Remove(Observerss observer){Observers.Remove(observer);}public override void Call(){foreach (Observerss observer in Observers){observer.Update();}}}public class Mouse : Observerss{public override void Update(){Debug.Log(\"猫叫了,我得跑了\");}}public class Person : Observerss{public override void Update(){Debug.Log(\"猫叫了,应该是饿了\");}}

然后,我们使用委托来实现这一功能

using System;using System.Collections.Generic;using UnityEngine;public class DelegateCat:MonoBehaviour{private void Start(){DeCat deCat = new DeCat();DePerson person = new DePerson();DeMouse mouse = new DeMouse();deCat.call += person.Notify;deCat.call += mouse.Run;deCat.call();}}public delegate void CallEvent();//声明委托类型public class DeCat{public CallEvent call;}public class DePerson{public void Notify(){Debug.Log(\"猫叫了,应该是饿了\");}}public class DeMouse{public void Run(){Debug.Log(\"猫叫了,我得跑了!\");}}

我们做一下对比:
1.发现实现同样的功能,使用委托非常的简洁,并且主题猫的类里没有任何观察者类成员,两者是完全独立的。而观察者模式双方并没有完全的独立,抽象主题通知时依然依赖于抽象观察者。

2.观察者不需要继承观察者接口,它的通知方法名可以不同,比如说人的执行方法是Notify(),猫的执行方法是Run(),现实中本就是这样的,方法名本就不一定相同。

也就是说观察者模式的缺陷为:

1.抽象主题依旧依赖于抽象观察者。

2.具体的观察者,通知方法被固定了。

观察者模式存在的意义就是解耦,它使观察者和被观察者的逻辑不再搅在一起。而是彼此独立,互不依赖。而使用委托则能使得观察者和主题完全解耦,甚至不需要知道对方的存在。

3.为什么要费这么大劲?
设计的核心思想:尽可能减少耦合,如果发现代码耦合,就要采取解耦技术,让数据模型,业务逻辑和视图显示三层之间彼此降低耦合,把关联依赖降到最低,而不至于牵一发而动全身。原则就是A功能的代码不要写在B功能代码中,如果两者之间需要交互,可以通过接口,通过消息,甚至是引入框架,但总之就是不要直接交叉写。

在软件工程中,模块的内聚和耦合是度量模块化质量的之一。

高内聚:是指模块是由相关性很强的代码组成,只负责一项任务,也就是单一职责原则,这样的模块,无论是从设计,实现,还是阅读,都能体现出保持专一性带来的好处。

低耦合:是指模块之间尽可能的使其独立存在,模块之间不产生联系不可能,但模块与模块之间的接口应该尽量少而简单。

耦合:在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应该使类和构建之间的耦合最小。

解耦:字面意思,就是解除耦合关系,在软件工程中,降低耦合度即可以理解为解耦,模块间有依赖关系,必然存在耦合,理论上的绝对零耦合是做不到,但可以通过一些现有的方法将耦合度降至最低。

4.委托?事件?封装!
这里我们只要对委托的例子做一个小小的变动

public class DeCat{public event CallEvent call;}

没错我们给声明前面加个event就可以了,但是你突然发现,编译不过去,有报错。这里也是事件与委托核心的区别。
事件是类或对象向其他类或对象通知发生的一种特殊签名的委托。它的本意是对委托的封装,它在外部只能被订阅或取消订阅,但是不能发布。一句话总结:事件只能在外部被订阅,但是不能在外部被触发。只能通过内部的公开方法,在方法内部触发事件,这样可以使得程序更加的安全。

这里我通过的例子来说明:当使用委托变量时,客户端可以直接通过委托变量触发事件,也就是直接调用
deCat.call();这将会影响到所有注册了该委托的变量。而事件的本意应该为在事件发布者在其本身的某个行为中触发,不如说在方法CallMethod()中满足某个条件后触发。通过添加event关键字来发布事件,事件发布者的封装性会更好,事件仅仅是供其他类型订阅,而客户端不能直接触发事件(语句deCat.call()无法通过编译),事件只能通过事件发布者DeCat类的内部触发(比如在方法deCat.CallMethod()中),换言之,就是call()语句只能在deCat内部被调用。这样才是事件的本意,事件发布者的封装才会更号。
以下是完整代码

using System;using System.Collections.Generic;using UnityEngine;public class DelegateCat:MonoBehaviour{private void Start(){DeCat deCat = new DeCat();DePerson person = new DePerson();DeMouse mouse = new DeMouse();deCat.call += person.Notify;deCat.call += mouse.Run;//deCat.call();//编译无法通过deCat.CallMethod();}}public delegate void CallEvent();//声明委托类型public class DeCat{public event CallEvent call;public void CallMethod(){call();}}public class DePerson{public void Notify(){Debug.Log(\"猫叫了,应该是饿了\");}}public class DeMouse{public void Run(){Debug.Log(\"猫叫了,我得跑了!\");}}

5.是不是还少了点什么?
关于委托事件的用法,网上搜一大堆,要多详细有多详细,我这里就没必要赘述了。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » C#学习(三)——委托与事件