浪迹福州

学习.net有一段时间,目前仍在学习中

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  6 随笔 :: 0 文章 :: 88 评论 :: 0 引用

2008年9月28日 #

    对于接口和委托,有人认为完全不搭,有人在某种情况下可能不确定到底该用哪个好。笔者抛砖引玉,和大家一起探讨。我们只讨论接口仅包含方法声明的情况,如果接口包含属性、索引器、事件等,与委托肯定不搭。一个仅包含多个方法声明的接口与多个委托要相互替换,在很多情况下都没有技术问题,只是怎样做更合理的问题。

    相对于委托来说,接口可以声明多个方法,而且接口名称可以很好的表示这些方法的相关性;接口在同一时间只能引入一种实现,也就是说类Client中有接口IUser型的成员,UserA和UserB都实现了IUser,那么在Client中只能调用UserA或者UserB的方法,要想同时调用A和B的方法,需要创建两个IUser型成员。

    相对于接口来说,委托类型只能“声明”一个方法;一个委托实例可以引入任意多个签名适合的方法,并且这些方法的顺序由委托实例所在类的外部决定,也就是说类A中有委托类型B的实例b(也可以是event),那么在Client1、Client2等多个类中都可以给A的实例a的成员b叠加方法,并且顺序由Client1、Clinet2决定。

    从某种意义上可以这样理解:接口给一系列类定义了规范,是面向对象的,但同一时间只能代表其中一个类;委托给一系列方法定义了规范,同一时间可以代表这些方法的任意顺序组合。一个仅包含多个方法的接口,可以拆分成多个委托来代替,这样耦合度更低,但是失去了相关性,面向对象的特性也随之失去。一个类中的多个委托实例也可以改成一个接口来代替,但会失去委托可以叠加任意多方法的灵活性,并且如果多个委托是不相关的,会造成理解困难(接口名称就很难定)。

    当接口只包含一个方法,委托只需叠加一个方法时,两者的作用是一样的。即使在这种场景下,我们也要明确,接口是针对类,委托是针对方法或某个地方发生了某件事,这样就自然会合理运用了。比如说FrameWork类库中,很多类实现了ICloneable接口,很多控件有OnClick事件。

 

    另外,业务层需要调用表现层的方法时,往往通过提供事件和表现层交互,多线程时还要用Invoke方式调用这个事件。如果业务层提供一个接口让Form实现,总觉得怪怪的,但是微软MVP模式的一种实现方法就是这样的,在P中定义接口让V(WebForm)实现,除了代码重用度提高外,和通常的WebForm做法还是一样的,和MVC有本质区别。

posted @ 2008-09-28 10:37 浪迹福州 阅读(1576) 评论(4) 编辑

2008年9月27日 #

    在笔者的首篇博文(http://www.cnblogs.com/zhangql/archive/2008/09/26/1299289.html)中,有下面一段表述:

    技术水平总能在扯皮和吹毛求疵中得到提高。如果从来不“求疵”,可能就不会知道if(str != "")不如if(str != string.Empty)高效、批量插入和删除的sql语句是要那样写才执行最快、接口和抽象类的区别不仅是语言层面、原来权限管理是要这样设计的、某个类那样设计职责才更单一更易于扩展……

    本来前两篇文章是学习cnblogs编辑控件用的,看到跟贴的朋友询问批量插入和批量删除的问题,决定整理成文和大家分享。

    我们这里讨论的只是普通sql语句如何写更高效,不考虑特殊的用文件中转等导入方式,毕竟在代码中调用sql语句或存储过程才更方便。

    批量删除很简单,大家可能都用过:

    DELETE FROM TestTable WHERE ID IN (1, 3, 54, 68)  --sql2005下运行通过

    当用户在界面上不连续的选择多项进行删除时,该语句比循环调用多次删除或多条delete语句中间加分号一次调用等方法都高效的多。

   

    本文重点讲述的是批量插入的写法:

    sql写法:

    INSERT INTO TestTable SELECT 1, 'abc' UNION SELECT  2, 'bcd'  UNION SELECT 3, 'cde'   --TestTable表没有主键,ID不是主键

    oracle写法:

    INSERT INTO TestTable SELECT 1, 'abc' From daul UNION SELECT  2, 'bcd' From daul    --TestTable表没有主键,ID不是主键

    曾经测试过,这种写法插入1000条数据比循环调用1000次insert或1000条insert语句简单叠加一次调用要高效得多,大概快20多倍(调试状态不是太准)。其实很简单,就用了个union(union all 也可以),但当时得出测试结果时还是很惊喜的。

    要得出这个结果需要两个条件:

    1、表不能有主键或者主键是数据库默认的(sql用自动递增列,oracle用序列)

    2、组合sql语句时只能直接用字符串连接,不能用参数化sql语句的写法(就是在组合的sql中用@parm做占位符,再给Command对象添加Parameter)

    以上两条任意一条不满足,效率的提高都不明显。

    另外,sql语句的最大长度有限制,组合几千条数据写成一条insert语句,可能会超过上限,所以如果有5000条数据,可以一次insert 1000条,分5次写完(不一定一次1000条最合适,有兴趣的朋友可以研究)。

 

声明:看了几位朋友的评论后发现自己没有叙述清楚

     上面的两个条件并不是说这样做就好,而是说当应用场景满足这两个条件时才适合用union的写法

     当时用这种写法时,是为了解决一个WinForm程序批量导入手机号码的问题,程序给自己人用,excel文件也是可靠的,不需要考虑sql攻击。还有一些场景是大量数据来源于通过Gprs网络连接的专用终端,总之数据来源是可靠的,还有就是数据量很大但不可能巨大。

 

 

PS:常常遇到一些小技巧或者值得注意的小细节,没有记下来,要等下次再遇到时才想起来。现在下决心要养成好的工作习惯,留此文为证

      能够整理成文的要写在项目组开发规范中,其它的至少要记录下来。

posted @ 2008-09-27 17:34 浪迹福州 阅读(8292) 评论(24) 编辑

    java和c#通常被认为是完全面向对象的语言,所有基本代码必须写在某个类中。但是,很多java和c#程序员编写的代码并不是真正面向对象的。有这种事?确实有,面向对象的编程语言只是提供了封装、继承和多态的机制,并不能保证我们用它写出的程序是面向对象的,即使我们把“人”和“狗”的代码糅合在一起,也不会导致编译和运行出错,我们来看一个c#编写的“人与狗的故事”:     

 

Code

 

    这段代码包含两个类Program和Story,单纯从语言层面讲,是面向对象的。但Story中把故事、狗和多个人等的代码糅合在一起(如果故事情节涉及板砖和商店的细节,还会更乱,而且不仅用到人的名字,还有年龄穿着等,故事有时间地点等等,为例子简单,没有提及),称为面向对象的设计,完全说不过去。

    如果需求不发生变化,这段代码不会有太大问题,没必要把类分的那么清楚。但需求还是变了,要求增加一个故事二:狗主人在另一天出门时遇到了朋友B,给B讲述了发生在前几天的故事一,并且讲述中他添油去醋,并没有按故事一的实际情节讲,我们来看代码:

 

Code

 

    这时代码已经变得无法忍受了,如果需求再变,不堪设想。也许有的朋友要说,需求不至于一直变吧,我想说:需求不变才不正常,因为需求是人类思想的反映,人的想法是不断变化的,整个世界也在变化。高楼大厦之所以没有经常拆了重建,并不是人们对它满意,而是反复拆建需要大量时间和金钱。而软件相对于建筑来说,只有设计过程(写文档和编码都是设计),没有建造过程,真正的建造过程是在代码写完后由计算机瞬间完成(编译成二进制exe、dll),所以人们只要不满意就可能让它“重建”。可以设想一下,假如代码完成后要由人工打纸带(像最初的程序,0打孔,1不打)来编译,或许随便找一个B/S程序够一个人打几年的,这时需求还是会变,但不会要求程序员去修改了——将就着用吧,不然就洗洗睡先。

    认同了需求是变化的,就要找办法解决问题,面向对象相对于面向过程的主要优势之一就在应对变化上,可能也是面向对象在系统软件开发方面流行的主要原因。上述代码有些过于初级,有一定经验的程序员可能会把代码写成这样: 

     Story有发生时间、地点等属性和GetStory方法,Person有被咬(被咬后的反映)和打狗等方法,如果故事情节涉及Brick(板砖)和Shop(商店)的细节,还会有这两个类,这样基本有了貌似单一职责的几个类。其实,这仅仅是对代码的“归类”,没有做到单一职责,比如Person中的BefallBite(被狗咬)方法会遇到这样的问题:

    如果Person实例是小孩,反映可能是坐在地上大哭

    如果是大人,可能是找板砖

    如果是狗主人家的小孩,狗可能只是轻咬他玩得,不会哭,会跟狗一起玩

    如果是大人,又是狗主人被咬,不一定舍得板砖拍……

    ……

    显然,Person还应该有一些子类:

    这样就基本有了职责单一的类结构。但我们还是发现了问题,Person的子类比较多,如果再加上“根据被狗咬的部位不同反映不同”(Baby被咬屁股不会坐地上哭,可能是趴着哭;大人被咬右手可能无法拿板砖),那么子类将会更多。这时仅仅靠最基本的单一职责原则及其它几条原则已经不能做出良好的面向对象设计,设计模式就是在面向对象的基础上进一步提高软件应对变化能力的“良药”。本例是典型的桥接模式应用场景,笔者将在后续博文中用更复杂更完整的例子和大家共同学习设计模式的综合运用。

 

PS 1:

    本文提到的三种编码方式应该可以代表三种编码阶段,在结尾提到了设计模式,其实在知道用设计模式后还有三个阶段,纯属个人看法:

    1、在单一职责等基本原则做的不太好时就接触了设计模式,根据各种模式定义的场景大量运用。这种阶段去做大项目,遇到的问题往往比最初级的混合编码阶段还多。

    2、基本功底打得扎实后,逐步学习运用设计模式,遇到问题能够根据设计模式定义进行思索,合理解决问题

    3、设计模式的定义经常不记得,设计和编码时只根据基本原则进行,遇到问题就重构代码,重构后发现好像和某种模式定义的场景类似,查书后确定是一样的……

   

    呵呵,设计模式最好不要强求,自然形成就好。

 

PS 2:

    刚开始写博客,对编辑器不熟悉,我贴的代码好好的,发布后对齐格式有点乱,反复几次搞不定;还有贴个类图要先截图存gif,再传上来,很麻烦。请高手留言指点,多谢!我用的编辑器是 TinyMCE(推荐)

posted @ 2008-09-27 10:55 浪迹福州 阅读(2836) 评论(22) 编辑

2008年9月26日 #

    java和c#都不支持类的多继承,相对c++来说,又都增加了接口的概念,一个类可以实现多个接口;和c++一样,java和c#保留了抽象类的概念。在编码中很多情况下接口和抽象类可以互换,并且都能正确编译和运行,GOF23种设计模式在不同的书籍上也出现了接口和抽象类混用的情况。从语言层面讲,把接口看成是仅包含抽象属性和抽象方法的抽象类,勉强可以说得过去。所以很多程序员分不清什么时候该用接口,什么时候该用抽象类,只好任选其一。

    首先,java和c#不支持类的多继承是很有道理的(类的单一职责原则),当你在设计时遇到必须用多继承的情景时,请检查你的类结构设计是否合理,或者把某些父类改成接口。

    其次,接口中声明的成员方法不能有任何实现,这样如果两个类实现这个接口,有可能两个类需要编写同样的实现。而用抽象类则可以把实现写在抽象的父类中。

    以上两点仅仅是语言层面上的区别,是显而易见的。如果做到良好的面向对象设计,把类和类关系定的合理,可能根本遇不到上述两种情况。我们来看一个场景:

    固定翼飞机、军用固定翼飞机、民用固定翼飞机(民航客机)、孙悟空、海鸥

    军用固定翼飞机、民用固定翼飞机,都属于固定翼飞机,有强烈的 is a 关系,所以应该从固定翼飞机继承;如果一架飞机是固定翼飞机,那么它不可能仅仅是固定翼飞机,因为它不是民用就是军用,所以固定翼飞机是抽象的,不会有它本身的实例,有的只是它子类的实例。所以固定翼飞机是 abstract class 。

    上述五类事物都会飞,但显然,飞机、鸟、会飞的人完全不是一回事,不应该让它们继承于一个抽象的AbstractFly类,所以固定翼飞机、孙悟空、海鸥都可以实现IFly接口,却不能从同一个抽象类继承。孙悟空实现了IFly接口,同时还可以和猪八戒等实现IChangeSelf(会变)接口。

    一个类可以实现多个接口,接口是没有实际意义的,所以即使一个类实现了再多接口,它还可以不违反类的单一职责原则。反之,继承于多个抽象类,大多会违反类的单一职责原则,要不然就是那几个抽象类设计的不合理。

    另外,根据依赖倒置原则,当高层模块和底层模块不得不耦合时,最好由高层模块定义一个“规范”,让低层模块来实现,而不是高层模块直接调用低层模块的类和成员方法。“规范”在很多时候用接口和抽象类都可以编译和运行,但通过前文的例子,我们很容易知道这时应该用接口,因为高层模块仅仅是定义了一个规范,没有实际意义,而且高层模块并不了解低层模块的类结构,不该强行为低层模块设置父类,影响低层模块实现的灵活性。

posted @ 2008-09-26 15:31 浪迹福州 阅读(2339) 评论(26) 编辑

    在Windows系统中,除驱动程序外,应用程序大概有如下几类:

  •     单机程序(只考虑主体功能,程序更新和注册等,也含网络通讯):WinForm,如:office、金山词霸、杀毒软件等;控制台程序;Windows服务等
  •     网络通讯服务器:可以是控制台、Windows服务、WinForm等多种形式;也可以是WinForm单机服务管理程序加上多个Windows服务的形式(如SqlServer等数据库服务端)
  •     网络通讯客户端:一般为WinForm形式,也有控制台的,或者网络游戏类的动画形式
  •     点对点网络通讯程序,其实就是一个程序既有服务端又有客户端功能,或者采用UDP协议通讯

    以上几类应用程序绝大多数都有可单独执行的exe,运行后产生一个或多个单独的进程,也有依托于rundll32.exe等进程启动的“可执行dll”程序(很多病毒木马和监控客户端属于此类)。

    显然,C/S程序中的服务端S和客户端C分别属于网络通讯服务器和网络通讯客户端类。而B/S呢?不是单机程序,只能属于网络通讯程序,但服务端和客户端都没有单独的exe,也就是说B/S并不是完整的程序,其客户端(browser)是IE等浏览器,服务端是IIS(Internet Information Services 网络信息服务平台,java采用tomcat等平台,本文只以IIS讲述,下同)。浏览器就是一个完整的网络通讯客户端了,一般都有单独的exe,IIS是一个WinForm的服务和配置管理程序加上多个Windows服务的组合(可能有操作系统的“亲密”支持),是一套完善的网络通讯服务器。

    我们用Asp.net等语言写的代码,编译后会生成一个或多个dll,以及aspx、js、css、图片等文件,其中脚本作为网络通讯客户端的一部分,由浏览器解释执行;dll在设置了虚拟目录后动态加载到IIS,作为网络通讯服务器的一部分,负责特定的业务处理。而浏览器和IIS间采用http头+html体(或其它变种)的协议进行通讯,当浏览器向IIS发一个字符串请求后,IIS根据aspx文件中对字符串的定义,提取控件值等信息,分发给特定的dll,由我们编写的dll进行业务处理,dll处理完后将新的控件值等信息以函数返回值之类的形式返回给IIS,IIS再根据aspx中的定义处理成协议字符串,回复给浏览器。

    所以B/S实质上是C/S的,只不过我们通常只做了服务端的业务处理部分,编译成dll交给IIS,客户端基本没做什么。单独的IIS也没有意义,必须有dll作为插件进行业务处理。

    B/S只能采用标准的http头+html体(或其它变种)的通讯协议,而C/S更加灵活,可以采用任何标准协议和自定义协议,甚至可以是基于UDP的应用层协议。当我们自定义的协议形成一个体系时,服务端可以做一个通讯框架,甚至是通用的exe平台,客户端界面由服务器取得,以后二次开发只需要做一个dll插入通用的服务器平台中即可。这样就把C/S程序做成了B/S的形式。Wap就是和Web一样的B/S类应用程序。

    写了这么多,发现C/S和B/S本质上是一样的,可以相互转化。

posted @ 2008-09-26 11:18 浪迹福州 阅读(943) 评论(3) 编辑

1、假设任何简单的细节问题其他成员都有可能想不到,要及时提出自己的看法。

    细节如解决方案命名、某个类和接口的命名、几个类间关系造成的扩展性问题等,只要与自己想法不一致,应及时提出。

2、假设其他成员都尊重自己,都会并已经认真思考了自己提出的问题。

    这样当提出的问题没有按自己的想法解决时,不至于影响情绪,不至于反复讨论无果,不至于在一个问题上浪费太多时间。

3、假设其他成员提出的问题都是经过认真思考的,应该给予重视。

    这样保证任何问题提出后都不会被轻易忽略。同时,要理解问题并不是针对某人提的,而是针对团队或某个项目。

 

    敏捷开发强调以人为本的理念,非常注重团队沟通,这三条假设也算是对团队沟通的一种阐述吧。强调细节有助于团队整体能力的提升,技术水平总能在扯皮和吹毛求疵中得到提高。如果从来不“求疵”,可能就不会知道if(str != "")不如if(str != string.Empty)高效、批量插入和删除的sql语句是要那样写才执行最快、接口和抽象类的区别不仅是语言层面、原来权限管理是要这样设计的、某个类那样设计职责才更单一更易于扩展……

 

 

注:红色字体版权所有,引用请注明

posted @ 2008-09-26 09:43 浪迹福州 阅读(1810) 评论(9) 编辑