Blog转移

      决定把BLOG搬到CSDN上去:http://blog.csdn.net/ericozh.谢谢大家来访问我的blog,也希望大家能常去我的新blog.

      

发布于 由 ozheric0 篇评论

面向接口的编程

    很久就想写这篇文章了,一直没有提笔,主要是怕自己把握得不好,毕竟,“面向接口”这个主题既有思想层面上的内涵韵味,也有具体实现上的操作技巧。

    这篇文章也就从这两个方面来谈吧:1 面向接口的思想;2 面向接口的实现。

    1 面向接口的思想

     Interface,这个词可以说是被我们用烂了的一个词,它大概是在软件设计开发整个过程中听得最多的一个词之一,尽管在不同场合其具体所指不同,然而,所有的“接口”都具有相同的特征:对外的一种暴露。我不知道说“暴露”这个词,你是否还是觉得有点不好听,那么就用“提供”这个词吧。举个例子吧,我们电脑的主机,被机箱盒给封装了起来,然后向我们提供电源按钮让我们开启机器,提供USB插口让我们连接U盘,提供网线插口,让我们连接网络,提供显示器连接插口,让我们连接显示器......这些插口,都是主机暴露给我们的接口,这些硬件接口给了我们操作主机的能力。试想,如果主机什么都不暴露,那主机就成为了一个完全封闭的东西,而一个封闭的东西与一堆垃圾一样毫无意义。

    从马哲的角度上来说上面这段话的意思就是任何东西都需要对外暴露“接口”来与其他东西发生联系,一个孤立的东西是没有存在价值和意义的。那么如何暴露接口呢?

   在企业管理,尤其是市场营销这一领域,我们常听到一个词:“面向客户”,在软件开发中同样如此。是的,我们之所以提供接口,目的是让“客户程序”(不管是你自己写,还是别人写的)来使用自己所写的程序。我们总是在谈组件化,组件化的本质就是将自身逻辑封装,让后对外暴露接口,组件的价值就在其暴露的接口是否有价值。在面向过程编程中,函数的声明是其对外提供的接口,文件的导入是为了对别人提供的接口的调用,文件的导出是为了对别人提供接口;在面向对象编程中,对象的非私有方法是对不同级别的对象的接口暴露。

   面向接口不象面向过程,面向对象那么直接明显,因为它是一种理念上的东西,高于面向过程,面向对象,又体现于这些编程方法中,这主要是有开发者自身去把握,是“无用”,又是“大用”。

  2 面向接口的实现

   具体的实现技巧我并不打算在这里涉及,因为不同的编程语言,不同的场合,其选择也不同,下面从通用层面上来讲,看看如何实现面向接口的编程。

   下面这个链接是我一个面向接口编程的概念图:

   http://community.hf-mstc.org/cs/photos/ozheric/picture3923.aspx

    在这个模型当中,可以分为三横三纵:横向代表层次,纵向代表功能。从横向来看,分为接口层,逻辑层,资源层。接口层是你对外暴露的接口的集合,比如你的类中的一个个公共成员,接口层对业务逻辑层的封装,提供给外界使用业务逻辑层的能力;逻辑层,也就是对自身业务逻辑实现的功能集,逻辑层对资源层进行调用,在实现自身业务逻辑的同时,也完成了对资源层的屏蔽,也就是说,实现了对资源层的封装;资源层,内容比较广泛,可以是别人提供的组件,也可以是诸如ADO.NET,Jdbc这样的数据资源访问层,但说白了,资源层还是别人提供给你的一些接口。 从纵向来看,上一层中的功能点都是根据业务逻辑调用下一层的接口,同时为服务的对象提供接口。

     在这种模型下,除了通过横向的分层使得思路清晰,还因为通过纵向的组织,使得测试工作也可顺利进行。比如,我们把接口层中的一个个接口分别作为测试单元,那么,通过对每一个测试单元进行单元测试,则能很好的定位Bug所在。

     面向接口的编程,内涵丰富,本文探讨得不是深入,只是给了一个大概的概念,希望能起到抛砖引玉之效,和大家交流更多相关话题。

  

  

  

   

发布于 由 ozheric1 篇评论

Java事件模型

      在我的前两篇介绍C#事件和委托的blog 发表之后,大家响应特别热烈,点击率很高,看来事件/委托机制是很多同仁比较模糊的地方,借此东风,加上最近自己转战java,于是决定写这篇介绍java事件机制的blog。

     其实,不管哪种语言的事件机制,毫无例外都逃不出三点:事件源/发送者,事件的接受者/处理者/侦听者,以及事件源向事件接受者传递的事件信息。对应在java中,事件源 (event source)事件倾听者 (event listener),事件消息称为eventobject。而在C#中,分别是发送者(Sender),处理者(handler),事件消息则是事件参数(EventArgument)。而java和c#都采用相同的响应模式:发布者/订阅者模式(publisher/subscriber),具体来说就是:

    (1)订阅者向发布者注册自己感兴趣的事件;

    (2)事件发生时,通知订阅者响应事件。

    简单一句话,就是那句常说的:“Don't call me,I'll call you。”

    由于之前已经对C#的事件机制进行了探讨,下面,我将着重谈谈java的事件机制,以及对两者实现机制的比较:

    (一)Java事件实现机制

下面是一个自定义java事件的例子,通过这个简单的演示,你可以看到Java的事件实现机制。这里说明一下,这个例子引自http://www.rainsts.net/article.asp?id=224,为了说明原理,改编了原文中对匿名方法部分,而且由于这个网站的代码编辑器缺乏对java代码的支持,因此,对关键字,类型等并没有作格式显示处理。

import java.util.*; // 定义一个类似 C# EventArgs 的类用来传递事件状态信息。 // 一般要求继承自 java.util.EventObject,且以 Event 结尾。 class ClicktEvent extends EventObject
{ public DemoBean source;
//构造函数的参数传递产生事件的事件源 public ClickEvent(DemoBean source)
{ super(source); this.source = source; } } // 通过接口来定义事件响应函数原型,就像c# delegate定义了响应函数的“模板”,
// 别忘了,接口实际上就是一种“合同”,“契约”,通过这个接口中的函数签名达到对响应函数的规范 // 一般要求继承自 java.util.EventListener,且以 Listener 结尾。
//这里以I开头定义ClickListener接口,借鉴自.net,不符合j2ee的命名规范 interface IClickListener extends EventListener
{ void click(ClicktEvent e); }
 
//事件侦听者,实现侦听者接口
public class ClickListener implements IClickListener
{
public void click(ClicktEvent e)
{
System.out.println( "the clicked event happened");
}
} // 定义演示控件类,也就是事件源 class DemoBean
{ // 用一个java.util.Vector对象来存储所有的事件监听器对象。
private Vector clicks = new Vector(); // 添加事件订阅。一般以 add( listener)方式拼写,并添加 synchronized 关键字。 public synchronized void addExampleListener(IClickListener listener)
{ clicks.add(listener); } // 移除事件订阅。一般以 remove( listener)方式拼写,并添加 synchronized 关键字。 public synchronized void removeExampleListener(IClickListener listener)
{ clicks.remove(listener); } // 触发事件。 protected void doClickEvent()
{ // 锁定,避免在触发期间有事件被订阅或移除。 synchronized (this)
{ // 创建事件状态对象。 ClicktEvent ce = new ClickEvent(this); // 循环触发所有的事件订阅方法。 for (int i = 0; i < clicks.size(); i++)
{ IClickListener e = (IClickListener)clicks.get(i); e.click(ce); } } } // 模拟点击操作。 public void Click()
{ doClickEvent(); } }
//测试程序 public class Program
{ public static void main(String[] args)
{ // 创建控件。 DemoBean bean = new DemoBean();
 
//实例化一个事件侦听者
ClickListner testListner=new ClickListner();
// 添加事件订阅。 bean.addExampleListener (testListner);
// 模拟触发点击操作。 bean.Click(); } }

 

输出结果:the clicked event happened 

(二)事件实现机制的比较(Java/C#)

     总体来说,c#沿袭了C/C++中的函数回调机制,通过委托对函数指针的封装来实现对响应函数的调用;而java则通过接口来规范响应函数,使用多态的方式在运行时实现对事件接收者的响应函数的调用,应该说,这才是一种面向对象的机制。当然,两种方式各有千秋,下表是对两者的比较:

java c# 说明
效率 采用多态,相对高 采用委托,相对低。 应该说,它们都是在运行时获取对哪个对象的哪个方法进行调用,但是采用多态相对于委托效率高一点。
是否支持静态方法调用

java采用多态,当然不能把方法声明为static;c#中delegate中的_object可以为null来实现对静态方法的调用
是否类型安全 它们都会在编译时对响应函数进行参数检查,类型安全。
开发者易用性 两级实体对象:事件源-与事件响应者 三级实体对象:事件源-委托-事件响应者 由于有delegate的封装,不用编写事件注册/注销之类的代码,c#事件处理易用性相对高;注意,虽然java中采用接口来规范响应函数,但这里却说java中是两级实体对象,是因为在运行时并不存在接口的实例(实际上,接口也不可实例化,呵呵)

 

     

发布于 由 ozheric0 篇评论

c#中的Delegate解析

      在我的上一篇blog<从面向对象编程的角度解析c#中的事件处理机制>中,我对c#的事件处理机制设计原理从面向对象的角度解析了其原理,文中最后提到委托是函数的模板,有几个读者回头就跟我说,既然它只是一个模板来负责规范事件响应函数的声明,那么,只要我的所有对该事件的响应函数都符合事件的函数调用声明的规范,那么,我就是不是可以不定义delegate了呢?很好,很好的一个问题,谁说不是呢?

      我的这篇blog正是为这个问题而作。

      在<从面向对象编程的角度解析c#中的事件处理机制>中的末尾,我提到public event BabyIllEventHander BabyIsILL这句声明通过c#编译器编译后,变成了如下可以语句:

//1 一个初始化为NULL的“私有”委托类型字段 private BabyIllEventHandler babyIll=null; //2 一个允许对象登记事件的add_*方法 [MethodImplAttribute(MethodOptions.Synchronized)] public void add_BabyIll(BabyIllEventHandler handler) { babyIll=(BabyIllEventHandler)Delegate.Combine(babyIll,handler); } //3 一个允许对象注销事件的remove_*方法 [MethodImplAttribute(MethodOptions.Synchronized)] public void remove_BabyIll(BabyIllEventHandler handler) { babyIll=(BabyIllEventHandler)Delegate.Remove(babyIll,handler); }

 

 下面,我们就从这里开始讲起。

    我们发现,我们寄予厚望----我们观念中觉得会很重要的一个关键字“Event”经过编译之后,没有了,而那个我们定义的Delegate : BabyIllEventHandler又冒出来,何也?或许这就是玄机之所在。

   “Delegate是函数的模板,Event是函数的容器”,这是我在<从面向对象编程的角度解析c#中的事件处理机制>中的表达。现实生活中,我们常说“容器”,是因为这个东西可以往里面装东西,可以往里面取东西。而在我们编程的概念中,一个Container往往有Add方法和Remove方法来实现对里面Object的添加,移出。如今,这个Event却没有这些方法,可见,它不是一个真正的容器。是的,没错,它是一层包装纸,是真正的容器的包装纸!真正的容器是那个Delegate。用技术的概念来说,Event是微软提供给我们的一个“语法甜饼”(syntactic sugar),这个语法甜饼把Delegate封装起来,达到更易用的目的。

  仔细看一看,事件的登记和注销是通过调用System.Delegate 的静态方法Combine()Remove()来实现对“容器”babyIll(Delegate :BabyIllEventHandler 的实例)的增加,移出。Combine() 将第二个 Delegate 结合到第一个 Delegate 中,传回此一新的 Delegate,转换类型之后重新赋给babyIll来实现增加Remove() 将第二个 Delegate 自第一个 Delegate 中移除,并传回此一新的 Delegate转换类型之后重新赋给babyIll来实现移出。如此说来,Delegate才是我们真正需要花大力气关注的东西。

    下面,我们来看看Delegate。

     我们先来谈谈回调函数,说说C语言中的吧。在学c语言的时候,我们只看到过函数的说法,现在冒出个回调函数是怎么回事?其实,回调函数本质上就是函数,在定义上是没有区别的。在编程过程中,有些函数是你写并被你自己调用,这就是我们常说的“过程函数”,而另一些函数是由你来写但是在某些情况下不光由你来调用还可能被系统调用或者是别人调用,这种函数就是“回调函数”。你的函数要被系统/别人要调用,那么你的函数就要遵守别人给出的接口规范,这就像我们c#中事件响应函数要符合事件源中定义的函数声明规范(也就是委托中的声明)一样。在设置回调函数时,将你的回调函数的地址(也就是函数指针)作为参数送给系统。当系统调用时(如事件发生,启动功能...),就自动会执行你的回调函数。可惜,该地址不会携带任何额外的信息,例如函数期望的参数个数,参数类型,参数的返回值以及函数的调用约定。也就是说,虽然系统/别人要求你要符合规范,但是因为没有在原理上规避,使得你可以通过一些特殊的处理方式达到挂接“非法”函数的目的,这就意味着,c语言中的回调函数不是类型安全的。

    委托是.net Framework提供的用来实现回调函数的机制,并且这种机制是类型安全的(微软不是总是宣称c#是类型安全的吗?这就是一例)。下面,我们来看看,它是怎么实现函数回调的,何以见得是类型安全的呢?

    我们先来看看Delegate的构造:

    在<从面向对象编程的角度解析c#中的事件处理机制>中,定义BabyIllEventHandler 这个委托类型的语句如下:

   public delegate void BabyIllEventHandler(object sender,BabyILLEventAgrs args);

   当编译器遇到这段代码时,它会产生如下所示的一个完整的类定义:

public class BabyIllEventHandler:System.MulticastDelegate {
//构造函数 public BabyIllEventHandler(object target,Int32 methodPtr);
//下面的方法和委托定义中指定的原型一样
//同步调用方法 public void virtual Invoke(object sender,BabyIllEventArgs args);
 
  //异步调用 public virtual IAsyncResult BeginInvoke(object sender,BabyIllEventArgs args,AsyncCallBack callback,object object); public virtual void EndInvoke(ISyncResult result); }

   你可以通过Reflector看一看,确实编译器产生了这样的一个类。包括一个构造函数,一个对委托函数的同步调用方法(Invoke()),两个异步回调(BeginInvoke(),EndInvoke())。我们还注意到这个类定义继承自System.Multicastdelegate。为什么是System.Multicastdelegate,而不是System.Delegate呢?这其中是有一段历史的,因为不是我们关注的重点,这里就不提了。

   所有的委托类型都继承自System.Multicastdelegate,在System.Multicastdelegate的成员中,有三个很重要的字段是我们需要关注的:

字段 类型 描述
_target System.Object 指向回调函数被调用时应该被操作的对象。在事件处理机制中,对应事件的响应对象。如果是响应函数是静态方法,它会被置为Null
_methodPtr System.Int32 回调函数的内存地址,也就是函数指针。在事件处理机制中,对应事件的响应函数的内存地址。
_pre System.MultiDelegate 指向另外一个委托对象。通过这个指针,形成了委托链

  通过描述中的说明,我们可以认识到,一个委托对象实际上是对调用时操作的对象和回调方法的一个封装。

      我们已经知道委托对象是怎样构造的,下面我们来看看回调函数是怎样调用的。这里只谈同步调用的情况,异步调用的情况可以参考相关资料。

      看事件定义:    public event BabyIllEventHander BabyIsILL;
      通过前面的分析,实际上BabyIsIll是委托:BabyIllEventHander 类型的,编译器知道BabyIsIll是一个指向委托对象的变量(严格来说,应该是指向一个委托对象容器的变量。这个容器是通过委托对象的_pre来指向前一个委托对象的,因此BabyIsIll指向最后一个委托对象即可),所以,它会产生代码来调用BabyIllEventHander 的Invoke()方法,这样,当我们使用BabyIsIll(this,e)来激发事件的时候,实际上是它会去调用Invoke()方法,当Invoke方法被调用时,它使用_target和_methodPtr两个私有字段在指定的对象(_target中指定)上调用期望的方法(_methodPtr中保存了方法的指针)。

     同时,注意Invoke()方法的签名和BabyIllEventHander定义的签名相匹配。正是因为这种配置关系,要求调用回调函数的时候进行了参数检查,返回值检查,微软才拍着胸脯大胆的说,这是类型安全的。

     [全文完]

      在这篇blog中参考了《.net框架程序设计(修订版)》第17章《委托》中的内容。另外,如果大家对事件处理以及函数指针感兴趣的话,可以看看台湾作家蔡学庸的《函数指针的进化论(1)》,绝对值得推荐的一个系列,有三篇文章,点击链接进入页面后面,其下方有2和3的链接。看完这三篇文章后,我总感觉,如果我不推荐给大家,就是我的错;但是,如果你不去看,或者不认真的看,那就是你的错。

    

  

发布于 由 ozheric0 篇评论

从面向对象编程的角度解析c#中的事件处理机制

摘要:c#中的事件处理机制是很多人学习c#过程中的难点。本文将从面向对象编程的角度解析微软的工程师们为什么会这样来设计c#事件处理机制。

1 现实生活中事件处理的三种模式

       现实生活中,我们说事件处理,常常是这样一个情况:一个人发生了某种变动(比如孩子生病了),然后另外一个人(当然也可以是本人,为了和前面例子对应,这里假设是妈妈)做出相应的对策(送他上医院)。在这个过程中,发生了消息的传递。请注意区分以下概念:孩子生病了这是事件源,妈妈在第一时间内是不知道的,她需要等孩子发条信息(比如孩子表现出咳嗽,发烧)过来才知道,也就是说,这中间发生了消息的传递。是的,正因为如此,事件处理机制在以前也叫消息处理机制。

  通过我们对现实生活中事件处理机制的分析,我们可以发现有三种模式:

 a: 事件源主动向事件接收者报告消息,接收者收到消息做出响应。这就像孩子主动告诉妈妈:“我生病了”。这种方式的特征是:主动报告。

 b: 事件接收者不断向事件源询问当前情况,是否有变动。就像妈妈问孩子,你是不是病了? 这种方式的特征是:事件源不主动报告消息,而是由事件接收者被动的不断的去询问。哎,孩子不懂事,没有办法,只能是妈妈多操心了。

 c: “保姆模式”:你想想,孩子生病了,这是多大的事啊!妈妈紧张,爸爸不也紧张吗?爷爷奶奶更紧张!那难不成爸爸妈妈爷爷奶奶整天守候在孩子旁边,不停的问:“孩子,你生病了没有?生病了咱上医院去。”且不说爸爸妈妈爷爷奶奶还有别的事要忙,这样不停的问孩子,孩子没病也会闹出个病来啊。哎,对了,请个保姆。保姆守候在孩子旁边,只要孩子生病,就立即通知爸爸妈妈爷爷奶奶叔叔阿姨亲姑舅嫁出去的表姐还有八百里表叔等等各位关心宝宝的人。呵呵,这保姆对孩子来说是“保姆”,对于各位关心宝宝的人来说,就是他们的公共秘书,只要孩子一生病,就通知大伙。至于是孩子主动告诉保姆自己病了,还是保姆不停的问孩子是否病了呢,那是保姆的事,否则,如果孩子病了,保姆没有通知大伙,那就是保姆的失职。

2  事件处理机制的技术实现。

    前面我们看了在现实生活中的事件处理机制,下面我们来看在程序设计中如何实现事件处理机制。

    事件处理,说白了,就是一个对象请求另一个对象执行活动,在程序中,也就是函数调用。不要以为c#中的事件处理机制就不是函数调用了,还是,而且:只能是,只会是。我们程序中所有的逻辑,毫无例外都是通过函数的调用来进行组织的。

   下面我们来仔细看看其具体的实现机制。 

//定义“生病参数”,此处仅考虑 “体温” public class BabyILLEventArgs:EventArgs { float Temprature; public BabyILLEventArgs(float temprature) { this.Temprature=temprature; } } //定义委托,注意委托是一个类,当然可以象类一样定义 public delegate void BabyIllEventHandler(object sender,BabyILLEventAgrs args);
//定义宝宝类 public class Baby { float Temprature; //事件
public event BabyIllEventHander BabyIsILL; //孩子生病函数 public void BabyFallIll() {
if (this.Temprature!=37)
{
//事件参数 BabyILLEventArgs e=new BabyILLEventArgs(temprature); //激发事件 BabyIsIll(this,e);
} } }
 
//定义大人类 public class Adult { public Adult(Baby baby) { //对“孩子生病”事件进行注册,只要孩子生病,就做出响应 baby.BabyIsIll+=new BabyIllEventHandler(GoToHospital); } //送去医院函数 public GoToHospital( object sender,BabyIllEventArgs e ) { if (e.Temptature>40||e.Temprature<35) { Console.Write("很严重,去省立医院就医"); return; } if(e.Temprature>39||e.Temprature<36) { Console.Write("比较严重,去市医院"); return; } else { Console.Write("病得较轻,去县医院"); return; } } }

  

    在上面的代码中,在Baby类中,定义了孩子生病事件,传递“体温”参数,在Adult类的构造函数中注册(也就是:登记)上事件,使得孩子的生病和大人的送去医院两个函数连接起来。连接的纽带就是那条:public event BabyIllEventHander BabyIsILL;语句中声明的BabyIsILL。它就是孩子的保姆,通过它调用所有的亲戚朋友(都是Adult的实例)的GoToHospital()方法。

    既然,事件处理机制仍然是函数调用,那为什么不直接调用呢?这就得从面向对象的角度来分析了,请看下节。

3 从面向对象编程的角度来理解c#中事件机制如此设计的理由

    我文章的题目说,要从面向对象编程的角度分析微软为何如此设计c#的事件处理机制,现在,就来看一下其中缘由。

   我们知道,面向对象编程的三大机制是:“封装”,“继承”,“多态”。第一条就是“封装”,封装要求你首先自己在内部把自己做好。好了,你把自己内部做好了,但任何对象在这个世界上都不是孤立的对象,需要与外界接触。你调用别人的方法,你的方法要被别人调用。你能调用别人的方法是因为别人向你提供了“接口”,你的方法要被别人调用,你就要向别人提供接口。你只要管你向别人提供你应该提供,你需要提供的接口就可以了。你不要管,也不用去管别人怎么调用你的方法。就像孩子生病了,孩子不要去管有多少人来照顾他,哪个医生来给他治病一样,孩子所需要做的就是告诉大家:我生病了。这里说的接口是一个广泛的概念,并不是我们程序定义中声明的“interface”。我们将我们的类,类的成员变量,成员函数声明为public,protected,internal。在给它们定义了一个安全限制级别的同时,更重要的是向相应的其它对象(public--所有;internal--程序集内部;protected--子类对象)“张贴”了一张“广告”:我的这些类,这些变量,这些方法,你们可以拿去用。换言之,你的所有可以被外界访问的东东,都是你提供的接口。

   事件正是这样一个对外的接口!!!我只要暴露出这样一个接口,我不管哪些对象来处理,它们怎么处理!!!正是因为如此,我们的Event前面常常声明为public,声明为private是没多大意义的。

    既然事件是一个对外的接口,那就要有对外的一个“规约”,一个“协定”,这个规约协定就是“委托”。事件要告诉外部对象,是从谁哪里发出来的事件,有哪些具体的事件信息,那么这个“委托”就要定义了事件源(Sender),事件信息(EventArgument)(说明:见评论“说明1”)。而对于要处理这个事件的对象来说,自己的函数无疑也要遵守这个“委托”约定。从函数调用来说,事件处理函数无疑也要符合这个格式,才能实现正确的函数调用。也就是说对于事件来说,委托充当着事件这个对外接口的函数声明的“约定”。另外一面,对一个事件的响应可以是多个对象的相应函数处理(就像孩子生病,爸爸妈妈爷爷奶奶等都要去医院一样),这些对象的事件处理函数又都要遵守这个“约定”,于是,委托又成为了事件处理函数的“模板”,模板的概念在面向对象中就是“类”的概念,所以,我们常说委托是“函数的类”。

       理解了委托,我们再会过头来看“Event”。说真的,“接口”还只能说是它的对外表现,其内在则是一个“函数的容器”。你想想,你暴露这么一个接口,别人遵守你的接口约定定义了函数,挂接上了你这个事件。你怎么在你事件发生的时候,调用这些人的这些函数?这就在声明事件的时候“Event”关键字的所在了。它里面肯定做了“手脚”来记住哪些人的哪些方法注册了我这个事件。是的,通过Reflector一看,你就知道了,一句简单的事件声明实际上变成了一个字段,两个方法,其中一个负责往字段上“挂接”(注册) 方法,另一个负责“移出”(注销)方法。比如,我们的那句public event BabyIllEventHander BabyIsILL声明,会在Baby类中变成下面这段程序:

//1 一个初始化为NULL的“私有”委托类型字段 private BabyIllEventHandler babyIll=null; //2 一个允许对象登记事件的add_*方法 [MethodImplAttribute(MethodOptions.Synchronized)] public void add_BabyIll(BabyIllEventHandler handler) { babyIll=(BabyIllEventHandler)Delegate.Combine(babyIll,handler); } //3 一个允许对象注销事件的remove_*方法 [MethodImplAttribute(MethodOptions.Synchronized)] public void remove_BabyIll(BabyIllEventHandler handler) { babyIll=(BabyIllEventHandler)Delegate.Remove(babyIll,handler); }

 

  能够往里面添加和移出方法,它不就是一个函数的容器吗?是的,这些函数都是符合委托中定义的格式。正因为如此,很多其他地方的讲解也常说是“委托链表”,但我更愿意说是“容器”,显然,“容器”比“链表”更宽泛,且不说里面是不是真的是“链表”结构,即便现在是,将来会不会是?Java会不会是呢?“容器”才是其关键所在。

    希望这篇文章,能帮助读者理解c#中的事件处理机制。谨以此文献给qinghu和qwliang,在我学习c#的过程中,他们对我的指导作用无可替代。

    

发布于 由 ozheric2 篇评论

神仙?妖怪?That's what Index in SQL SERVER(1)

最近我们学籍项目组由于对提高数据库查询速度的需要,想到通过对数据库中的某些表中的某些字段建立索引。但是,对于“索引”这个东西,一直关注太少,还真颇有几分神秘色彩。查阅了一些资料,有点认识,与大家分享。说明一下,我将分两个部分来谈“索引”,这篇Blog是第一部分,谈了索引这个词的来历和C#中的索引器;下篇Blog是第二部分,详细讨论Sql Server数据库中的索引机制。

1 说文解字谈“索引”

    汉代文学家许慎著有《说文解字》,对字,词的来源逐一剖析,如庖丁解牛般将字词大卸八块,酣畅淋漓。“索引”这个词我们也不妨来解一解,说一说。

“索”,常跟“绳”组成一个词:“绳索”,为什么?“绳”是“绳索”的中间部分,也就是那条“绳子”;“索”,代表了“绳子”要“绑”在其它东西上才能发挥绳子的作用。怎么“绑”?当然是“索”住:套个圈圈住目标。

好,现在有一条绳子绑住了目标,我们在绳子的另外一头,我们的目标被大量的东西遮盖住了,我们怎么找到目标?当然是顺着“绳子”跟过去,通过绳子的“引导”找到目标。能够“顺藤摸瓜”,完全是因为有这个“绳子”的引导,这也就是“引”。

“索”和“引”合在一起组成了“索引”这个词。通过对这个词的解析,我们可以认识到“索引”向我们指出了一条“明路”使得我们不用“众里寻它千百度”,也不用等到“蓦然回首”的时候,而是直接就朝“灯火阑珊处”走去,发现目标。

在我们的应用中,当“索引”当作动词用时,与“查询”并无多大差别;当作名词用时,则相当于“查找路线”之意。当我们对一大堆东西建立索引,比如新华字典中的“索引”,无非就是对这堆东西进行编撰目录,提供一个简单的界面,以方便查找。

2 C#中的“索引器”

   在谈数据库中的“索引”前,先来谈谈C#语言中的“索引器”。C#语言中有“含参属性”这样一个概念,通常我们也称之为称“索引器”。何来此叫法?我们不妨也来分析一下。

  “含参属性”,顾名思义,这个属性的定义带有参数。我们知道,一个属性是对另一个或者多个变量的包装,提供了一个访问接口。我们需要参数来干什么?如果属性包装的变量是一个,那么当然没有需要参数的必要,因为属性和变量是一一对应的;当这个属性包装的是多个变量呢?外部代码要通过属性这个接口来访问这多个变量中的一个,那就要提供一个参数以定位是对哪一个变量进行访问。这个“参数”提供了“定位”功能,也就提供了一个快速在一堆对象中找到目标的功能,这就是“索引”。正因为如此,“含参属性”也称之为“索引器”。更确切来说,它提供了对一组对象的线性访问能力,使得属性包装的集合对于外部代码就像是一个数组一样,通过参数就可以找到集合中的目标。

发布于 由 ozheric1 篇评论

做报表统计的几点收获

在刚刚过去的做报表的一段时间里,一个人拥有一个工程,一个人静静的思考面对的问题,一个人细细的雕琢自己的代码。出现问题,思考或者寻求帮助,发现思路,然后推翻原有代码结构体系,在重构中又有新的认识,形成新的思路,然后又开始整理代码……这种经历,以及这种经历带来的感觉真的很好。而在之中形成的认识收获,对于自己以后的编程无疑具有很好的指导意义。

下面我就谈谈我的一些收获,主要谈如下几点:

1 程序按层次进行组织;

2 封装和重用;

3 异常处理;

4 重构。

 

程序按层次进行组织

最底层的函数实现具体的统计细节,完成如统计判断,累加等操作。上层的函数按照调度逻辑实现对底层函数的调用,然后继续被上一层代码调用。越往上层越远离具体实现细节,越抽象,越往下层越接近细节。在每一个函数中的语句都处于同一个层次。如何判断一个函数中的所有语句是同一个层次呢?看上一条语句和下一条语句和本身是否平级,是否存在“观念”上的先后关系。一个函数只干一件事,实现一个逻辑,函数里的语句恰好反映了实现这个逻辑的先后顺序。

程序按层次进行组织的好处在于一旦某个时候在某个地方的调度逻辑发生改变,只需在对应的函数进行变更。比如这次统计报表的实现,由于我之前是按照层次进行代码组织的,后来,yongxu说某个地方逻辑不是那样子的,结果我只用了一点点时间就调整过来了,因为我只需要对负责实现这个细节的函数进行修改就行了,没有出现“牵一发而动全身”的情况。并且由于分层组织,使得逻辑清晰,代码可维护性好。

我们很容易犯的一个错误就是过早的陷入细节。是的,细节的实现使得我们可以很快地看到效果,而且有时实现细节的过程似乎有点激动人心的,毕竟完成细节意味着我们把任务完成了大半。无可否认,这样做会让我们很快的把心放下来,但是常常是这样作,使得实现细节和调度逻辑混合,细节掩盖住调度逻辑,之后我们又不愿花点精力把细节抽取出来,还原调度逻辑,最终层次变得混乱,后期维护变得困难。

 

封装和重用

如果说生活中的事情我们将很多任务交给同一个人好比将很多鸡蛋放在同一个篮子中,一旦篮子坏了,所有鸡蛋就打碎了,风险很大;那么,对于编程,则确实需要我们将所有鸡蛋尽可能的放在同一个篮子中,一旦需求变更了,只要换个篮子就行了。这就是代码重用的好处。

比如:在统计的逻辑实现过程中,把一些公共的逻辑抽取出来,封装在帮助类中;把不确定的过于细的逻辑通过函数“丢”出来,实现对变化的隔离,等等。这里附带提一下,并且对于实现同一功能的函数,我们可能需要暴露不同的接口形式,以前我通过函数重载的方式来解决,但是在每个重载的函数里都给与了具体的实现。实际上,我们可以将所有实现细节放在一个大而全的函数中,其它函数只是传递的参数数量或者参数值不一样,以后维护只要维护这个大而全函数就可以了。这个做法是组长clyin告诉我的,实际上,这也是微软的做法。

 

异常处理

一个人拥有一个工程,意味着要一个人控制程序执行过程中可能发生的异常。特别是这个工程是通过Windows Service来调度的,一旦发生异常就使得服务停止,这无疑不是我们所希望看到的,必须将所有异常“扼杀”在工程里面:让异常刚一冒芽,就“掐”掉。然而,我的代码中又要利用NOD来访问数据库,也就是说我要调用第三方的代码。

为此,对于每一次调用第三方代码,我都要对结果进行校验,是否有为Null的。对于获取出来的结果,比如一个学生,每一次利用其子对象的属性,我要判断其是否为Null,防止抛出“操作对象没有引用实例”的异常,等等,总之,调用别人的代码,别人的代码对我来说,就是“黑盒”。同时,我自己的上层代码对自己的下层代码的调用同样当作是“黑盒”来处理。尽可能确保每一步都能够“正常”进行,一切纳入正常轨道,而不会发生“异常”。

 

重构

当我有了一个新的思路之后,我就迫不及待的将代码实现。这样带来的好处是对瞬间灵感的把握,然而,同时,代码的质量也降低了。命名的规范,代码的层次,参数没有校验等等在编码之初都显得毫无章法,于是,不得不开始重构。然而,在重构中,最大的收获倒不是将函数名字从“Get”改为“Set”,不是从一个方法内部提取出一个独立的方法,而是在重构中,对自己之前思路的再认识,往往在这个过程中,又激发了新的想法,发现新的问题,得到新的思路。很享受这种感觉,在不断的重构过程中,体会着自己思维的变化,对问题的认识一步步深刻,解决方案的一步步改善,这种感觉真的很好。

 

发布于 由 ozheric0 篇评论

“报表统计,舍我其谁?!”

前段时间,一直在做报表统计,很有收获。这是我第一次编写报表统计相关的程序,从最初的惶恐,到过程中的点点揪心,到最后完成工作的舒心,一路走来,别有一番滋味在心头。有句话说:“痛快”何以为“快”?全因“痛”字在先。痛过之后的快乐,岂是“欢快”所能比?如是这般,这回,我算是“痛快”了一回。从这篇文章题目你也可看出,我现在该有多“痛快”。是的,我就这样,当我感觉自己在某个方面有那么点收获,我就无法抑制内心的兴奋,变得狂妄,一点也不知道收敛。

 “独乐乐,与众乐乐,孰乐?”这份痛快,不敢独享,遂拿出自己做报表统计的一些体会,与大家分享。在这里,我也得感谢qwLiang,没有他的指导参谋,我可能跌倒在了起跑线。感谢dcdingclyin,他们给与了我充分的信任,并给了我足够的时间来思考碰到的问题,没有这么长时间一点一点的“磨”,我不会有这么多的认识和体会,不会思考那么多。

 

    首先说明,我的统计不是利用sql语句实现的,而是在程序中实现的。我要统计的报表都是二维报表,解决方案的大致思路是把二维报表看作是一个矩阵,然后通过遍历查询出来的结果集,对每一条记录分析应该对二维报表的哪一行,哪一列进行累加操作。这样做的好处在于我们不再需要对每个单元格建立一条查询语句,由于得到哪一行,哪一列都是变量表示,使得我们的代码具有很大的伸缩性,我们不用维护那么多条sql语句。好吧,下面我将详细介绍我的问题和解决方案。下面的这个报表是我这次进行报表统计过程中的一个,我作了一些精简,去掉了几行,目的是要清晰的说明问题了。

     1 要统计的报表举例:

学生变动统计                                                               单位:人

上学年初报表在校学生数

      

       

本学年初报表在校学生数

退

死亡

非正常

1

2

3

4

5

6

7

8

9

10

11

12

13