问题描述
|
当一个类不能(或不应该)做某事时,事件或委托可以作为一种解决方案。
说
class President
Event AskedQuestion(QuestionEventArgs)
Delegate GetAnswerToQuestion
class Scientist
AnswerToQuestion()
// delegate approach
myPresident.GetAnswerToQuestion = AddressOf myScientist.AnswerToQuestion
// called once myPresident need it
// event approach
myScientist.AnswerToQuestion(questionEventArgs) Handles President.AskedQuestion
{
// executed once president asked a question
}
在这种委托方法中,如果一位校长提出一个问题,然后由科学家做出回应,则由总统班级直接使用科学家方法。
在.NET Framework代码中,我没有观察到,但是直接使用了委托。直接使用它是错误的,如果可以,为什么?
解决方法
直接使用它是错误的,如果可以,为什么?
不,这没错。
这是我的想法。委托字段是事件,字符串字段是属性。也就是说,您可能具有:
class Car
{
private string modelName;
public string ModelName { get { return this.modelName; } }
...
逻辑上,模型名称是汽车的属性。当有人问您驾驶哪种汽车时,您说“福特福克斯”时,您是在描述汽车的属性。您不会将\“ Ford Focus \”视为\\“ field \”或\“ string \”,而是将其视为某种汽车的名称。在计算机程序中,字符串字段只是名称存储方式的实现细节。该属性可以是字符串,也可以是枚举,或其他任何形式;关键是从逻辑上讲,汽车具有型号名称,而不是字符串字段。
事件和委托是相同的方式。汽车可能会发生“爆炸”事件(也许您正在编写视频游戏!),爆炸事件由委托类型的字段实现。爆炸是汽车逻辑上要做的事情;委托字段是实现事件的机制。
那么直接使用委托人是“错误的”吗?不,当然不是。直接使用字符串是“错误的”。有时您需要操纵不是属性的字符串,有时您需要操纵不是事件的委托。
诀窍是编写清楚将机械流程与业务流程区分开的代码。如果您发现自己在属性逻辑中混合了很多字符串逻辑,或者在事件中混合了很多委托操作,那么您可以考虑尝试将机制代码与业务代码进一步分离,以便更容易看出哪个是哪个。
,框架中有很多使用委托的方法。 LINQ就是一个明显的例子:
var result = someCollection.Where(input => input.MatchesSomeCriteria);
“ 3”采用具有特定签名的委托,该签名被调用以确定是否在结果中包括项目。最常见的用法是如上所述的lamba方法,但您也可以传递一个方法:
string[] nums = new[]{ \"1\",\"2\",\"3\"};
int sum = nums.Select(int.Parse).Sum();
int.Parse
与这种情况下Select
期望的委托委托签名(Func<string,int>
)匹配,因此将为nums
中的每个字符串调用它。
通常,当直接使用委托时,会将它们用作消耗它们的方法调用的输入。虽然在某些地方它们是消费者状态的一部分(例如,HttpListener
具有一些属于委托类型的属性),但数量并不多。
,与代表进行原始合作可能需要一些
样板代码(定义委托,声明必要的成员变量并创建自定义
注册/取消注册方法以保存封装等)。
浪费时间,另一个问题是在原始应用中使用委托作为应用程序的回调
机制是,如果您不将类的委托成员变量定义为私有,则
调用方将直接访问委托对象。如果是这种情况,呼叫者将能够
将变量重新分配给新的委托对象(有效地删除当前的函数列表以
呼叫),更糟糕的是,调用者将能够直接调用代理的调用列表。
,大事记
事件实际上是.net的真正原因之一,因为它使您可以声明一个更加简洁的接口。您可以有一个宣布需要答案的总裁类,而无需将其绑定到应答代理的实现,例如
interface IPresident
{
event Action<QuestionArgs,IPresident> HasQuestion;
void RecieveAnswer(QuestionArgs,Answer);
}
然后在你的科学家班上
partial class Scientist
{
public Scientist(IPresident president)
{
president.HasQuestion += TryToAnswerQuestion;
}
private void TryToAnswerQuestion(QuestionArgs question,IPresident asker)
{
if(CanAnswerQuestion(question))
{
asker.RecieveAnswer(question,GetAnswer(question));
}
}
}
如果新班级要回答校长的问题,他们所要做的就是监听事件,即有一个问题需要回答,然后如果有能力回答。如果科学家想回答其他人的问题,我们只需要实现一种附加到他们事件上的方法即可。
直接委托调用
上面概述的委托方法的问题在于,它破坏了封装。它紧密结合了科学家和总裁的实现,并使代码变得脆弱。当您有其他人回答问题时会怎样?在您的示例中,您将需要修改自己的Scientist实现以添加新功能,这被称为“脆性”代码,这是一件坏事。这种技术在合成中确实有一定作用,但只有很少(如果有的话)是最佳选择。
linq的情况有所不同,因为您没有将委托公开为类/接口的成员。取而代之的是,您将其用作调用方声明的函子,以使您知道调用方感兴趣的信息。由于您正在进行“往返”封装,因此封装保持不变。
这使您可以定义非常干净且功能强大的API。
我们可以以科学家为例,并使用此技术对其进行扩展,以使某人找出我们可以回答的问题
partial class Scientist
{
public IEnumerable<QuestionArgs> FindQuestions(Predicate<QuestionArgs> interest,IPresident asker)
{
return this.Questions.Where( x => interest(x) == true && x.IsAuthorizedToAsk(asker))
}
}
// ...
partial class President
{
FirePhysicists()
{
foreach(var scientist in scientists)
{
if(scientist.FindQuestions(x => x.Catagory == QuestionCatagory.Physics,this).Count != 0)
{
scientist.Fire();
}
}
}
}
注意FindQuestions
方法如何使我们不必实施其他代码来询问没有传递委托的能力的科学家。虽然这不是查找直接调用的委托的唯一情况,但这是最常见的委托之一