用一个缓存控制的例子来让你了解委托以及事件的真正意义

xboxeer 2012-06-08 11:13:02
刚看到一个关于委托顿悟的帖子 原帖 但是总感觉这样的例子呢 对于不懂的人来说可能仍旧不懂 懂的人呢觉得都是错误(原帖挑刺的大神很多啊)重点是实际写代码的时候呢可能还是不知道什么时候改用委托 该怎么用(所谓理论和实际的代沟)如果是事件的话 估计更加不知道怎么去用了 这贴可以算是刚刚提到的帖子的补充吧 帮助读者理解事件与委托的区别 以及如何在实际中使用
说实话那个时候我对委托也是顿悟的 与那个帖子不一样的是 我是在学习ajax的时候学到ajax回调的时候突然间顿悟了 呵呵 算是殊途同归吧
废话不多说 先来看看这篇帖子用到的委托的例子 就当是需求描述吧

设想你做了一个三层架构 其中用到了缓存(非页面缓存) 对于缓存的控制呢我一般是放在BLL层 不过我这边叫他Service层
对于缓存我们要做哪些控制呢?
首先 对于第一次访问的数据 我们会把这些数据放到我们的ASP.NET缓存中 对于执行增删改了的操作 我们需要对缓存进行更新 但是具体怎么进行控制 对于基类是不好预测的 那么我们就可以把它放到子类里面 进行具体实现的推迟 这样子就衍生出了另一种做法 就是把这些控制方法定义为抽象方法 让子类去实现 但是!!有些数据量不大的操作 我并不需要缓存 而且可能这些控制方法也不是子类来定义的 那么我们的这种将实现推迟到子类的方法就行不通了 这个时候 就是委托起作用的时候了 其实对于委托我个人的理解是这样的 把不知道怎么做的事情 交给别人去做 你只要调用他就可以了 即:回调
下面上代码:
此层的接口定义是这样的 其中的QueryResult<T>类其实就是对操作结果的一个封装
IQueryServiceBase

public interface IServiceBase<T>
{
List<T> Query(int startIndex, int pageSize, ref int totalCount);//分页查询
List<T> GetAll();
T GetByID(int id);
}


以上都是完整的接口定义 下面讲的时候我只针对一个事件 Add来讲 毕竟都是差不多的
先还是看下一个实现了接口的类ServiceBase所包含的成员:

#region Event

public delegate void OnDataChanged(object sender, DataChangedEventArg<T> e);//定义一个委托类型 他可以作为事件

public event OnDataChanged DataChanged;//定义一个事件 该事件类型呢就是我们刚刚定义的委托 一会儿我们会解释为什么要用事件而不是直接将委托暴露出去

protected void CallEvent(List<T> eventData)//事件的调用方法 eventData就是我们的事件参数 更好的理解呢 应该是 事件发生时候的上下文
{
if (this.DataChanged != null)
{
var eventArg = new DataChangedEventArg<T>(eventData);//事件参数必须从EventArg类基础 你可以为他添加别的属性 代表事件发生时候你要传递给事件订阅者的事件上下文 本例中 实际上就是你在进行CRUD操作时所涉及到的数据
this.DataChanged(this, eventArg);
}
}

#endregion

上面的代码定义了我们的事件 要记住事件就是一种特殊的委托 说他特殊 是因为他必须有固定的方法签名 而且不可被订阅者直接调用 通俗点儿 对于事件 我们只可以进行两种操作:订阅与取消订阅 所谓订阅 就是之前提到的 对于事件的发布者(这里的ServiceBase类)不知道怎么完成的操作 由订阅者完成了 但是呢 订阅者不能触发事件 也就是上面的DataChanged 如果你直接在订阅者处(也就是ServiceBase类之外)直接调用ServiceBase的DataChanged事件 编译器是会报错的 这就是刚刚说到的 Event是一种特殊的委托 因为如果是委托 我们是可以直接在ServiceBase之触发这个委托的
总结起来 就是 Event是只可以由事件发布者来触发(注意这里用了触发 而不是调用) 而委托呢?谁都可以触发。
为什么要这么做呢 这里就考虑到我们之前的事件参数 虽然ServiceBase不知道如何进行缓存控制 但是他必须知道哪些数据必须交给具体控制的代码 这里就是事件的第二个参数EventArg的作用 他就负责了将这些数据传递给具体实现的代码。
上面撤了这么多 那么我们来看下事件的触发代码 注意 这里用的是触发 什么时候触发事件 对于事件的订阅者是不知道的 这个是取决于事件的发布者

public virtual List<T> Query(int startIndex, int pageSize, ref int totalCount)
{
var returnValue = _search.Query(startIndex, pageSize, ref totalCount);//这个_search变量是ServiceBase的字段 其实就是个数据访问组件 这里也可以理解三层架构的意义 我们在底层数据层只做基础的CRUD 返回一致的对象List,Service层呢则会进行一些别的操作 比如我们的缓存控制
CallEvent(returnValue);//在执行分页查询的时候 触发了我们的事件 虽然这里形式上是一种方法调用
return returnValue;
}

上面的代码是事件触发代码 在我们的ServiceBase里 下面呢就是订阅者的代码 这里呢是一个NewsQueryService类 基础自ServiceBase类 负责新闻查找之类的操作 自然涉及了具体的缓存控制

public NewsQueryService(NewsTemplateQuery repository)//构造方法 参数是数据层的类
: base(repository)
{
this._repository = repository;
this.DataChanged += new OnDataChanged(NewsService_DataChanged);//订阅事件
}
public override List<news> Query(int startIndex, int pageSize, ref int totalCount)
{
if(缓存存在)
{
return 缓存内容;
}
return base.Query(startIndex, pageSize, ref totalCount);//缓存不存在 则执行具体的查询操作 并由于调用了基类的Query方法 所以实际上会触发DataChanged事件 但是必须知道 触发的类是ServiceBase类 而不是这边的NewsQueryService类
}

void NewsService_DataChanged(object sender, DataChangedEventArg<news> e)//实现具体的缓存控制操作
{
var news = e.Data;//在之前的ServiceBase触发事件的时候传入的数据 保证了事件触发传递的数据只能是由事件发布者提供 在这里 就代表了具体查询到的新闻
//下面的代码呢 就是一些具体的缓存控制代码 比如说更新缓存 移除缓存 甚至可以做一些延迟绑定之类的操作
}

上面既是一个完整的例子 这边呢 ServiceBase和NewsQueryService是父子继承关系 换句话说 利用抽象方法 我们一样可以做到这种延迟实现 那么如果说NewsQueryService包含了一个ServiceBase类 是一个b has a关系呢? 此时就非得用事件来做了

总结一下 委托的意思呢 我的理解 应该是把不知道怎么做的事情,经常变化的事情封装起来 交给别人去做 你直接调用别人实现了的就可以 虽然用抽象方法也可以做 但是这就形成了一种局限 也就是这个不会做的类和知道怎么做的类必须是一种继承关系 而委托则取消了这种局限 使得任何关系的类都可以实现这种所谓实现的抽象 其实面向对象 设计模式很重要的一点 就是将变化封装
至于说函数指针的 实在是害人不浅 没学过C++的能知道函数指针是什么么?而且就算知道了,又有什么用呢?
至于事件 他呢是一个特殊的委托 特殊在 他的调用只可以是发布这个委托的类 这样就使得触发时候的上下文可以保证正确(我们的例子中就是需要进行缓存控制时涉及到的数据) 而这也解释了为什么事件必须要有两个特殊的参数 一个就是我们实际的事件触发者sender,另一个就是触发事件时包含的数据EventArg

头一次写这种教程类的文章 还请各位大神拍砖!!!

...全文
446 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
jieshao4 2012-08-14
  • 打赏
  • 举报
回复
菜鸟,看起来狠吃力啊!
叫我三三 2012-06-11
  • 打赏
  • 举报
回复
这个订阅者模式不错
Ki1381 2012-06-11
  • 打赏
  • 举报
回复
总结一下 委托的意思呢 我的理解 应该是把不知道怎么做的事情,经常变化的事情封装起来 交给别人去做



我也这么理解。封装的时候委托就是在需要的地方划个圈圈里面写上“拆”,至于之后调用的时候用户抹去圈圈换成板砖,或者改造成码头停航母都不关我事了
xboxeer 2012-06-11
  • 打赏
  • 举报
回复
不过正确的讲 page类就是包含了一系列事件 事件呢也就是特殊的委托 但是page本身并不是委托
[Quote=引用 12 楼 的回复:]

page自己定义了一系列事件 你应该知道asp.net中所有页面都是继承page的 每次你写的所谓按钮点击代码其实是指按钮被点击了应该怎么做 这其实就是指按钮点击事件具体的完成是由Page类委托给你了 但是按钮是什么时候点击的 你是没法控制的 这个就是page类来做的 他知道按钮什么时候被点击 然后调用他委托给你来做的事情
引用 11 楼 的回复:

这样看。一个网页就是个委托。网页中的……
[/Quote]
华丽的笔调 2012-06-11
  • 打赏
  • 举报
回复
了解了。
cjh200102 2012-06-11
  • 打赏
  • 举报
回复
GOOD
xboxeer 2012-06-11
  • 打赏
  • 举报
回复
page自己定义了一系列事件 你应该知道asp.net中所有页面都是继承page的 每次你写的所谓按钮点击代码其实是指按钮被点击了应该怎么做 这其实就是指按钮点击事件具体的完成是由Page类委托给你了 但是按钮是什么时候点击的 你是没法控制的 这个就是page类来做的 他知道按钮什么时候被点击 然后调用他委托给你来做的事情
[Quote=引用 11 楼 的回复:]

这样看。一个网页就是个委托。网页中的方法:LOAD CLIKC就是事件?
[/Quote]
华丽的笔调 2012-06-11
  • 打赏
  • 举报
回复
这样看。一个网页就是个委托。网页中的方法:LOAD CLIKC就是事件?
ooo7880000 2012-06-11
  • 打赏
  • 举报
回复
写的很不错呀。。对我很有帮助。。
qkhhimkfrgw 2012-06-11
  • 打赏
  • 举报
回复
感觉楼主介绍的重点更像是缓存哇
五更琉璃 2012-06-11
  • 打赏
  • 举报
回复
看完了 感觉你这篇和顿悟那篇差不多,只是你这里委托应用环境复杂了点而已
xboxeer 2012-06-11
  • 打赏
  • 举报
回复
直接复制黏贴肯定不行~
[Quote=引用 18 楼 的回复:]

看的时候以为看懂了,按你的方法在vs测试一遍 发现一头雾水╮(╯_╰)╭,看了水平还没够
[/Quote]
叫我三三 2012-06-11
  • 打赏
  • 举报
回复
看的时候以为看懂了,按你的方法在vs测试一遍 发现一头雾水╮(╯_╰)╭,看了水平还没够
xboxeer 2012-06-10
  • 打赏
  • 举报
回复
野比大神留言 顿时感觉如春风拂面~
[Quote=引用 8 楼 的回复:]

前排留名,人工置顶
[/Quote]
xboxeer 2012-06-10
  • 打赏
  • 举报
回复
头次写这种 把握不好尺度啊~~只是想找一个实际可能用到的例子 而不是说什么吃饭啊睡觉啊之类的作比方 那个时候我看的是这种例子但是看完还是不知道什么时候用委托~
[Quote=引用 6 楼 的回复:]

LZ,对于零基础的来说,这篇文章貌似有点专业性强了点吧
[/Quote]
renyiqiu 2012-06-10
  • 打赏
  • 举报
回复
LZ,对于零基础的来说,这篇文章貌似有点专业性强了点吧
xboxeer 2012-06-09
  • 打赏
  • 举报
回复
这就沉了么?桑心啊~
xboxeer 2012-06-09
  • 打赏
  • 举报
回复
这就沉了么?桑心啊~
xboxeer 2012-06-08
  • 打赏
  • 举报
回复
其实已经缩减了 本来还想顺带提一下如何用委托来做延迟绑定 苦于组织不好文字啊
[Quote=引用 1 楼 的回复:]

好长啊
我是来看评论的
[/Quote]
SocketUp 2012-06-08
  • 打赏
  • 举报
回复
好长啊
我是来看评论的

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧