C# ~ 从 委托事件 到 观察者模式 - Observer
委托和事件的部分基础知识可参见 C#/.NET 基础学习 之 [委托-事件] 部分;
参考
[1]. 初识事件 到 自定义事件;
[2]. 从类型不安全的委托 到 类型安全的事件;
[3]. 函数指针 ~ C#中的委托(Delegate)和事件(Event);
[4]. C# 中的委托和事件 - 张子阳;
C# 中的委托和事件(续) - 张子阳;
委托
委托本质是一个密封类,定义方法的类型,将方法作为方法的参数。委托包含一个具有相同签名和返回值类型的有序的方法列表(调用列表)。
委托声明:public delegate void MyDel(string str); 编译结果为:
public sealed class MyDel : System.MulticastDelegate
{
public MyDel(object @object, IntPtr method);
public virtual void Invoke(string str);
public virtual void EndInvoke(IAsyncResult res);
public virtual IAsyncResult BeginInvoke(string str, AsyncCallback callback, object @object);
}
扩展:自定义委托类 MyDel 继承于类 MulticastDelegate,MulticastDelegate 类是 System.Delegate 的子类,支持多路广播委托并维护对应的委托列表。两个常用 public 属性:
· Target:委托调用的方法所属的类实例的引用,若方法是静态方法,则为 null;
· Method:委托所表示的方法的信息;
构造函数中的参数:object 为对象实例的引用,methodPtr 用于标识回调方法,分别对应 Target 和 Method。一个常用 public 方法:
· public Delegate[] GetInvocationList();委托的调用列表中方法的数组;
参考:C# - 委托链;委托的本质论;
1. 创建委托(对象)
[修饰符] delegate 返回值类型 MyDel(参数列表);
MyDel objDel1 = new MyDel(obj.实例方法); 或 MyDel objDel1 = obj.实例方法;
MyDel objDel2 = new MyDel(Class.静态方法); 或 MyDel objDel2 = Class.静态方法;
· 为委托对象分配内存;
· 把方法添加到委托对象的调用列表中;
2. 组合委托 (Combining Delegate)
委托是恒定的,委托对象被创建后就不会再被改变。委托组合拷贝的是操作数的副本,只有相同类型的委托才可以组合。委托的组合和移除分别用"+"和"-";
MyDel objDel = objDel1 + objDel2;
3. 调用委托
objDel(参数列表); 或 objDel.Invoke(参数列表);
用于调用委托的参数会去调用调用列表中的每个方法。
· 调用带返回值的委托:委托调用的返回值是其调用列表中最后一个方法的返回值。
· 调用带引用参数的委托:调用委托的参数会随着调用列表中方法的调用而改变。
注:Invoke 是同步方法,BeginInvoke/EndInvoke 是异步方法,但其调用者 myDel 的调用列表有且只能用一个方法,而 Invoke 没有这个限制。Invoke and BeginInvoke 介绍;
IAsyncResult res = myDel.BeginInvoke(委托的参数列表, null, null);
myDel.EndInvoke(res);
4. 匿名委托
匿名委托可以访问其所在方法的局部变量,具体 .Net委托和事件 - 匿名委托部分;
5. Action<> 和 Func<> 以及 Predicate<>
将委托的声明和赋值合并,进一步简化。Action<>没有返回值,Func<>有返回值。
// 委托的几种赋值方法均可用在此处
Func<int,bool> myDel = (Lambda表达式);
Func<int,bool> myDel = new Func<int,bool>(函数名);
Predicate<T> 是返回 bool 的泛型委托,可以理解为 Func<T,bool>的泛型委托的别名,多用于查询的条件表达式。
参考
[1]. 从委托到匿名委托到Lambda表达式再到 Action<>和Func<>;
[2]. 深入分析委托与事件;
事件
参考:深入理解事件的本质;
对象一是事件产生者或发送者,对象二是事件接收者或订阅者,对象一产生消息,对象二响应并处理消息(事件/消息机制)。
event 是 delegate 的高级形式,事件封装了委托,委托封装了方法。事件包含一个私有委托,事件提供对私有控制委托的结构化访问,当事件触发时,调用委托来依次调用调用列表中的方法。在事件中,委托是事件的发起者 sender 将 EventArgs 传递给处理者的管道。
1. 事件声明
保存和调用事件处理程序。
public [static] event 委托类型 Notify;
推荐使用:事件使用标准的预定义委托类型
public delegate void EventHandler(object sender, EventArgs e);
2. 事件注册方法
· 方法形式
[1]. 实例方法:publisher.Notify += subscriber.实例方法;
[2]. 静态方法:publisher.Notify += Subscriber.静态方法;
· 委托形式
[1]. 实例方法:publisher.Notify += new EventHandler(subscriber.实例方法);
[2]. 静态方法:publisher.Notify += new EventHandler(Subscriber.静态方法);
· 匿名方法
publisher.Notify += delegate(object source, EventArgs args) { … };
· Lambda表达式
publisher.Notify += (source, args) => { … };
注:publisher 是发布者类对象;subscriber 是订阅者类对象,Subscriber 是订阅者类。
3. 事件访问器(Event Accessor)
事件访问器表现为 void 方法,add 和 remove 访问器均包含隐式值参数 value。通过为事件声明自定义的事件访问器,此时事件并未内嵌委托对象,需要自定义添加和移除注册事件的方法来自行封装一个委托的实例。事件的 add 和 remove 不能缺且必须有实现主体但可以为空。
private MyEventHandler notify = null; /// [1] 声明委托的实例
public event MyEventHandler Notify /// [2] 事件封装委托的实例
{
add /// 增加
{
if (null != value)
notify += value;
}
remove /// 删除
{
Delegate[] funList = notify.GetInvocationList(); /// 获取调用列表
if (-1 != Array.IndexOf(funList, value))
notify -= value;
}
}
参考
[1]. 字段访问器 ~ 事件访问器 ~ 索引器;
[2]. C# 事件访问器;
函数指针 vs 委托
回调函数,Callback,在函数体内调用主调用函数中的函数。
· 函数指针:保存函数的入口地址,作为函数的参数、用于调用函数;
委托:保存函数的入口地址同时保存调用该函数的类/类实例的引用;
· 委托扩展性更好,支持多播委托(MulitCast)和异步调用;
委托 vs 事件
· 委托支持"+"和"-"、"+="和"-="、赋值运算符"=";事件仅仅支持"+="和"-=";
· Delegate 是类(型),Event 是成员,Event 成员类型派生于 Delegate;
· 委托常用来表达回调,事件表达外发的接口;
· 委托:可以在类外部触发、允许直接通过委托调用相应的处理函数:委托对象(参数列表);
事件:只能在类内部触发、通过发布者类提供的 public 方法去调用。
注:事件包含一个私有的委托对象。 事件的封装性和易用性更好。
public MyEventHandler Notify1;
public event MyEventHandler Notify2;
其实,Notify1 相当于 Class 里面的 字段Field,访问级别 public ,Notify2 相当于 属性Property,访问级别也是 public,但是,Notify2 内部封装了一个 访问级别为 private 的 委托对象!(带 event 关键字,编译之后,委托对象变成 private,并自动生成一个与委托对象对应的事件)
属性封装字段,事件封装委托。
参考
[1]. Event 详解;
[2]. 谈 C# 中的 delegate - hyddd - 博客园;
[3]. 不惧面试 - 关于委托;一个委托的例子;
观察者模式
Observer Pattern,即 Subject-Observer,又称监听模式 (Source/Listener) 或 发布-订阅模式(Publisher-Subscriber),模式中的皇后。Observer 模式定义对象间的一(Subject)对多(Observer)的依赖关系,当一个对象状态改变时,依赖于它的其他对象会被自动告知并更新。Observer 模式一对多将依赖具体转化为依赖抽象,是一种松耦合的设计模式,但抽象类Subject仍然依赖于抽象类(接口)Observer。逻辑关系图:
注:事件处理程序即被委托的方法。Observer 模式典例:报纸订阅、短信分发、热水器,可参考 委托和事件详解 - 补充;
Java API 内置的观察者模式:
- java.util.Observable -- Subject 被观察者
- java.util.Observer -- Observer 观察者
参考:
问题
· 如何限制事件只允许一个客户订阅?
将事件声明为 private,然后提供两个 public 方法用于注册和取消。或利用事件访问器。
· 如何获得多个订阅者的返回值?
利用 Delegate 类的静态方法 Delegate[] GetInvocationList(),委托/事件变量调用之,然后遍历包含委托方法的数组即可。
C# ~ 从 委托事件 到 观察者模式 - Observer的更多相关文章
- Observer设计模式中-委托事件-应用在消息在窗体上显示
Observer设计模式:监视者模式.在类中的方法中处理的结果或者消息通过事件委托 的方式发送给主窗体. 因为在其它类中直接访问主窗体类,显示内容是不能直接调用控件赋值的,当然也有别的类似查阅控件名, ...
- C#委托与事件之观察者Observer设计模式
前言 委托: 委托是一种在对象里保存方法引用的类型,同时也是一种类型安全的函数指针. 或委托可以看成一种表示函数的数据类型,类似函数指针. 事件是特殊的委托 观察者模式:两种角色:(1)Subj ...
- [工作中的设计模式]观察者模式observer
一.模式解析 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 观察者模式又叫订阅发布模式, ...
- C#设计模式——观察者模式(Observer Pattern)1
一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...
- C#设计模式——观察者模式(Observer Pattern)
一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...
- c#委托事件及其讲解
一定要标明出处,波哥的文章.所有文章都值得一看.这篇是摘抄的大白话之C#事件讲解.委托 http://www.cnblogs.com/wudiwushen/archive/2010/04/20/170 ...
- 观察者模式(Observer Pattern)
一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...
- c#设计模式之观察者模式(Observer Pattern)
场景出发 一个月高风黑的晚上,突然传来了尖锐的猫叫,宁静被彻底打破,狗开始吠了,大人醒了,婴儿哭了,小偷跑了 这个过程,如果用面向对象语言来描述,简单莫过于下: public class Cat { ...
- Unity C#笔记 委托&事件
C#的委托与事件搭配,即是观察者模式的一种实现. 因为观察者模式的原理很易懂,不作多讲,本文纯粹用于记录语法. delegate(委托) //声明没有参数,没有返回值的委托类型XXXX public ...
随机推荐
- Expert 诊断优化系列------------------语句调优三板斧
前面三篇通过CPU.内存.磁盘三巨头,讲述了如何透过现在看本质,怎样定位服务器三巨头反映出的问题.为了方便阅读给出链接: SQL SERVER全面优化-------Expert for SQL Ser ...
- Hadoop学习笔记—14.ZooKeeper环境搭建
从字面上来看,ZooKeeper表示动物园管理员,这是一个十分奇妙的名字,我们又想起了Hadoop生态系统中,许多项目的Logo都采用了动物,比如Hadoop采用了大象的形象,所以我们可以猜测ZooK ...
- 完成AngularJS with MVC 5, Web API 2项目
经过接近两个月的日夜奋战,完成AngularJS with MVC 5, Web API 2的项目,这也是进入公司以后最大的一个项目,从项目需求.用户Prototype/Demo,招人,开发完成,可谓 ...
- [译]Asp.net MVC 之 Contorllers(二)
URL路由模块 取代URL重写 路由请求 URL路由模块的内部结构 应用程序路由 URL模式和路由 定义应用程序路由 处理路由 路由处理程序 处理物理文件请求 防止路由定义的URL 属性路由 书接上回 ...
- 使用JAVA编写电话薄程序,具备添加,查找,删除等功能
//该程序需要连接数据库.根据word文档要求所有功能均已实现.//大部分方法基本差不多,//在查询修改的时候能输出 最大ID号 和最小ID号,并且可以对输入的ID号进行判断是否存在(具体方法请查看 ...
- 基于redis实现可靠的分布式锁
什么是锁 今天要谈的是如何在分布式环境下实现一个全局锁,在开始之前先说说非分布式下的锁: 单机 – 单进程程序使用互斥锁mutex,解决多个线程之间的同步问题 单机 – 多进程程序使用信号量sem,解 ...
- LINQ系列:Linq to Object生成操作符
生成操作符从现有序列值中创建新的序列. 1. Empty Empty操作符返回一个指定类型的空集. 1>. 原型定义 public static IEnumerable<TResult& ...
- windows自带记事本导致文本文件(UTF-8编码)开头三个字符乱码问题
在windows平台下,使用系统的记事本以UTF-8编码格式存储了一个文本文件,但是由于Microsoft开发记事本的团队使用了一个非常怪异的行为来保存UTF-8编码的文件,它们自作聪明地在每个文件开 ...
- Android之TextView的样式类Span的使用详解
Android中的TextView是个显示文字的的UI类,在现实中的需求中,文字有各式各样的样式,TextView本身没有属性去设置实现,我们可以通过Android提供的 Spannab ...
- 配置putty自动登陆服务器
putty是一款知名的SSH工具,可以用来登陆linux服务器,提供了终端.SSH是secure Shell的缩写.我之前也有一篇文章介绍这个话题:http://www.cnblogs.com/che ...