幸福清扬

之技术学习

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  16 随笔 :: 3 文章 :: 0 评论 :: 0 Trackbacks

2008年7月29日 #

 

小点滴:之间考虑程序性能的时候,总是用TickCount来计算自己算法的耗时问题,但是它只能精确到毫秒。看CLR via C# 留意到 StopWatch 这个类,就是专门干这个事的,不错,精度还高,以后就用它了。

 

接口:

1、 规定:C#支持多接口继承,但不支持多类继承。

2、 C# 接口可以定义方法签名外,还可以定义属性、事件、索引,因为它们背后对应的都是方法。但是C#不支持静态方法(尽管CLR支持,但CLS不支持)。

3、 接口可以相互“继承”,其实这里的继承的味道已经变了,而是有点“联合约定”的味道。

4、 C#编译器要求接口的实现必须声明为public,并且CLR要求该方法应该声明为virtual,如果你没有在源代码上显示标注virtual关键字,编译器会在编译时为其添加virtualsealed关键字,这阻止子类覆写该方法;如果你显示标注了virtual在源码处,编译器是不会再添加sealed关键字的,所以这就允许了子类覆写该方法。

对于sealed接口方法,尽管子类不能覆写,但是子类可以再次继承同一接口,然后实现自己的方法,在具体应用的时候,会调用哪一个就看变量类型了。(但是要注意,应该给子类的同名方法加new 关键字,使其屏蔽父类的方法。)

5、 接口的显示实现:通常情况下,继承接口的类只要实现一个和接口一样方法签名的函数就可以了,编译器会知道两者之间的关联;另外一种情况,通过添加接口类名显示指明实现了哪个接口,这该情况下,不允许对该函数实现获取修饰符(如publicprivate),也不允许使用virtual修饰,编译器会将其编译为privat virtual sealed,也就是说既不能继承,也不能通过类的实例变量调用,只能通过接口变量的方式调用到它。书上还说其,不是一个正常的类方法,通过查看IL代码发现在函数体内多了.override [mscorlib]System.IDisposable::Dispose这么一句话,具体情况还没搞懂。

6、 泛型接口:这里有两点我感觉比较有意思,一是说如果有泛型,就尽量不要用以前的非泛型,因为非泛型为了实现通用性,参数变量往往是object类型,存在安全隐患和拆装箱子的问题。之所以还存在大量的非泛型应用是因为考虑向前兼容。二是继承泛型接口可以因为type的不同,而对同一泛型接口当作两接口。例如:

// this class implements the generic IComparable<T> interface twice

       public sealed class Number : IComparable<Int32>,IComparable<String>

       {

              // 代码省略

}

7、 泛型条件限制:不多说了,这属于泛型的问题,但是可以解决值类型装箱的问题,具体查书。

8、 解决两个接口存在同名函数(方法签名也相同)的问题,可以使用接口显示声明解决。

9、 书上说使用显示接口有很多问题,慎用!看了看确实,如果不是非使用其不解决的问题,就尽量不用。

10、关于设计基类还是设计接口的问题:基本原则是IS-A 还是Can-Do,从书上的意思好像比较倾向于说基类设计好。(具体情况具体分析吧)。

posted @ 2008-07-29 17:26 杨连国 阅读(5) | 评论 (0)编辑

2008年7月3日 #

     在说明生成器模式之前,不得不说一说它和抽象工厂模式的区别:两者看起来很相似,都是应对一系列“对象”的变化,而抽象出一个公共接口。不同处在于抽象工程所要创建的一系列对象尽管是相关的,但各自是各自,他们之间的具体应用关系由客户决定;而生成器模式对应的一系列对象是在一个“大对象”下的必要组成部分,也就是说,我们关心的是要得到一个“大对象”,而抽象工厂所关心的是要得到一系列对象。另一个显著的区别是,抽象工厂模式得到一系列对象的使用“算法”由用户决定,而生成器模式是用固定的算法组织生成的一系列子对象,来生成一个所要的对象。

     回归到生成器模式上来,先说动机(也就是要用模式的原因)

     在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对固定。(引用李建忠视频)

     上述原因说出了变化,和不变。和抽象工厂的动机比较,我们会发现多出来了“固定算法”和“要得到的是一个复杂的大对象”。既然是这样,我们就可以再定义一个组织者类,来描述算法,我们还是用抽象工厂封装变化部分。鉴于我们要得到的并不是子对象,所以只需要对外发布一个生成大对象的方法就是了。

      因为还未实际使用过该模式,所以说对这个“用算法构成大对象”,具体应该是个什么样,还缺乏感性认识。

     再琢磨一下GoF的定义:
 
     将一个负责对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

           ————GoF

     哦,忘了说缺点了,如果固定的算法不再固定,就不能应用该模式。确切的说所有的模式共有的缺点就是,当假设不变的部分发生了变化,该模式就不适用。

     探讨:将固定算法放在抽象build接口内可不可以呢?
posted @ 2008-07-03 14:36 杨连国 阅读(85) | 评论 (0)编辑

     类型的成员种类:没什么好说的
 
     类型的可见性:也没什么好说了,友元程序集可以在需要时了解一下

     成员的可见性

          需要知道 assembly <--> internal  family <--> protected 对应关系

          C#要求基类和子类对同一成员量的访问权限必须是一致的,例如在基类中是protected的成员变量,继承到子类后,依然是protected。而CLR却不是这么规定的,可以将protected变为public。

     静态类

          结构不能声明为静态的,因为CLR天生要求它必须实例化。

     部分类、结构、接口

          需要说明的是partial关键字是C#提供的,CLR并对此并不知晓。不错的属性!

     组件、多态和版本控制

          组件软件编程(Component Software Programming,CSP)

      如何调用虚方法

          在CLR内部调用方法有两个指令call 和 callvirt。
          call可以用来调用静态方法、实例方法和虚方法。调用静态方法是必须指明调用方
法的类型,调用实例方法或虚方法必须指明变量引用,call指令假定变量不为null。  callvirt指令用来调用实例方法和虚方法,JIT编译器在编译调用该指令时,会验证对应的变量是否为null,则抛出异常。  为什么C#编译器不只生成call指令呢?这是因为c#编译器团度觉得应该做null检查,尽管这样会牺牲一定的效率。  那是不是对虚方法的调用都会使用callvirt指令呢?有两种例外,第一种是子类覆写虚方法时调用基类的该方法,例如base.somemethod(),这是会使用call指令,因为使用callvirt会造成循环调用。第二种情况是对结构方法是用call指令,因为值类型天生就是不能为null的。

          另外需要注意,如果一个方法之前定义为非虚方法,而已经被广泛调用,就不应该再将其改为虚方法,因为之前的调用使用的是call指令,除非重新编译,否则会产生不可预期的结构。但是如果使用C#编程倒是不用担心这一点,因为C#编译器总是在调用实例方法时使用callvirt指令

     类型和成员可见性

          Jeffrey建议定义一个类时,缺省应该具有sealed关键字,并说出了几个原因,原因就不多说了,说的也对,如果仅仅是为了封装,而不需要继承,应该是sealed。但是C#缺省并无该关键字,也就是C#定义一个类,那么该类肯定可以继承,除非显示的添加了sealed关键字。但是在C#代码内,如果不加任何前缀修饰,默认是internal

     类型版本控制:说了一下new的问题,就不啰嗦了

posted @ 2008-07-03 12:46 杨连国 阅读(20) | 评论 (0)编辑

2008年6月26日 #

大家都说,CLR via C# 翻译的不好,也就没买,看英文原版,一边看一边想写笔记,但是懒惰让自己一直懒于动笔,随便写写吧,总比不写强。

    每种语言都有其基本数据类型。
    C#也不例外,但是这里不同之处在于多出了个.net平台。
    所以,C#每个基本数据类型都对应一个FCL类。但不一定都兼容CLS。

    C#规范说要尽量用C#约定的基本类型表示符,Jeffrey不这么认为,他认为应该尽量使用FCL提供的具体类名,这样不会产生歧义。

    另外关于基本数据类型之间的转化,Jeffrey说,这个不能遵循类继承关系规则了,编译器知道该怎么办。(佩服老外就在于这种地方,这个事他会拿出来单独给你说说)

    转化规则不细说了,因为我自己用C#,有句话必须说“在小数转化为整数时,C#总是舍掉小数部分”    数据溢出检查不多说了,缺省不检查。

    引用类型、值类型:

    之前一直没有太过于留意FCL提供的类中,哪些是引用类型,哪些是数字类型,还一直以为除了基本类型和枚举类型意外都是引用类型,Jeffrey倒是给提了个醒,说一般称其为结构的是值类型,结构是值类型这我清楚,可是一直没留意“结构”这个词。

    内存分配的事就不多说了,比较清楚。

    这里说说自定义值类型,通常也就是结构了,Jeffrey说,如果定义结构,那么其内定义的字段在程序运行期间不会被修改,或则说不能对其修改。刚开始不理解这句话的用意,看到后来的装箱和拆箱才明白了,因为很多人意识不到拆装箱,或则说有时候有潜伏的拆装箱操作,如果对内存分配和拆装箱基本原理不很清楚,往往会有不正确的赋值预期。另外定义的结构,尽可能的要小,如果要大点也可以,但就不要当参数传递了,因为这会带来性能损失。
    老实说,什么时候应该将一个类定义为结构,自己也学习过,但是上两点说法倒是头回听说。

    拆箱、装箱:
   
    老话题了,看上一章的时候了解到引用类型,有两个特殊的变量,一个就是类型指针,另外一个就是同步索引,这里我想说的是同步索引,因为有同步索引的存在所以,引用类型可以用来做同步锁,而值类型不可以,原因就在于此。(似乎懂了)

    泛型的问题暂不多说了。

    关于明显的需要类型转换的地方,发生拆装箱子,是易于理解的。但是对值类型调用方式时发生的潜在拆装箱倒是没注意,例如 Point p;p.GetType();就会发生装箱行为,因为GetType方法直接继承于Object,而值类型重载的虚方法或自己的方法调用则不会发生装箱行为。

    对象Equal检查:

    object提供的Equal虚方法,用来识别两个对象是否是指向同一个对象,但是Jeffrey说,如果你真是这个目的最好不要用Equal虚方法,最好用静态的ReferenceEqual,为什么呢?因为虚方法是可以被覆写的,覆写后是否还是这样一层意思就不一定了。例如,你可以判断两个对象是否值相等,基本数据类型就是这么覆写的,字符串也是这么覆写的。所以是比较两个对象是否相等还是指向同一个对象,是要仔细计较一下的。

    object Hash Code

    对哈希表为什么叫哈希表一直不清楚,对object的GetHashCode方法,更是不知道怎么用。
    看过这一节之后,略有明白:

    Microsoft为了实现对不同对象的区分,以方便将其放在HashTable内,通过hashcode找对对应 键/值 对,所以为object设置了这样一个GetHashCode的方法,获取一个唯一的哈希码区分,object内部的算法不清楚,但是它提供的是虚方法,你自己可以覆写自己的区分方式,当然这要遵循一定的原则。另外,如果覆写Equal方法的话,就一定要覆写GetHashCode方法,原因是hashtable判断两个对象是否Equal需要用到该方法,这里的对象是指key对象。

    需要特别说明的是,这里获取的hash code 可以在某次应用中用于区分对象,但是不能将某次获取的hashcode作为永久标志应用,它是会变的(例如算法的重写)。

    如何写获取HashCode的方法,以及实现规则就不多说了。
posted @ 2008-06-26 12:42 杨连国 阅读(140) | 评论 (0)编辑

之前对抽象工厂模式理解不透彻,困惑,看了李建忠的讲解,收获如下:

        1、单纯从GoF的定义,并不能理解其用意;
   
        2、要从动机去理解,这里有两点:

        一系列相互依赖的对象的创建
        由于需求的变化,存在更多系列对象的创建。

    ------------------------------------------------------------

    李建忠总结要点如下:

    1、如果没有应对“多系列对象构建”的需求变化,则没有必要用抽象工厂模式,这时候使用简单工厂完全可以了。

    2、系列对象指的是这些对象之间有相互依赖或作用的关系。

    3、abstract factory的缺点是难以应对“新对象”的创建,也就是在这一系列的对象确定下来之后,最好就不变了,只是增加不同系列的这些对象,这是其优势所在,如果增减了对象,对该模式反而是灾难。

    ------------------------------------------------------------

    另外个人觉得其引入思路比较好:

    首先是,没有变化,不需要额外封装;
    如果有了变化,哪里有变化,就封装哪里;
    这里抽象工厂模式应对的是对象创建有变化,那就封装对象创建。
    为了实现不依赖于new,出现了工厂,让工厂统一创建对象;
    为了实现对象实例不依赖于具体对象类,出现了抽象对象类,所有具体的对象类都继承于该抽象类。
    同理,为了产生不同系列的对象,需要不同的工厂,那么就可以抽象一个抽象工厂,让所有具体工厂继承于抽象工长,如此一来,客户程序就只依赖于抽象类,并能应对变化了。
    变化不会消失,只会转移,变化到哪去了呢?对抽象工厂的赋值,这里是逃不掉的。

    ------------------------------------------------------------

    最后还是引出GoF的定义:

    提供一个接口,让该接口负责创建一系列“相关或则相互依赖的对象”,而无需指定它们具体的类。
                        ——《设计模式》GoF

    以及其UML图
   
抽象工厂模式


posted @ 2008-06-26 09:59 杨连国 阅读(115) | 评论 (0)编辑

2008年6月20日 #

 

1、 使用C#提供的static readonly可以一句话实现单件模式。

2、 静态构造函数不会在多线程内触发。

3、单件模式的核心思想是如何控制用户使用“new”,而不仅局限在一个变量一个实例
posted @ 2008-06-20 16:47 杨连国 阅读(13) | 评论 (0)编辑

 

1、 请注意GoF《设计模式》的副标题是——可复用面向对象软件的基础。所以说设计模式并等于面向对象的设计模式,这是常被忽略的。而我们常说的设计模式往往是指面向对象的设计模式,所以应该先掌握好的是面向对象设计的基本思想和原则,而后才是模式。

2、 面向对象的原则和设计模式的目的是为了“应对变化,提高复用”。这一点也往往被忽略,对于基本上不需要变化的代码套用设计模式,是过度设计,无用功。所以在考虑模式和原则前应该先识别变化。

3、 设计模式不宜先入为主。一上来就是用实际模式是设计模式最大的误用,重构到模式是被认为一个不错的方法。

4、 设计模式能否得到良好的应用在于对其意图的深刻理解,简单套用框架是不对的。

posted @ 2008-06-20 16:45 杨连国 阅读(14) | 评论 (0)编辑

2008年4月22日 #

用户体验、逻辑结构、安全、性能

 逻辑结构:可复用性、可扩展性、灵活性、松耦合性

 前四点是我认为程序开发的四个目标、后四点是我认为程序结构设计的目标。

 OO 设计原则我认为是实现上述目标的 参照原则,

 而 设计模式则是为了实现目标、遵循原则的 具体战术。

 废话:

 为了重构数传软件,花力气学了一阵子设计模式,跃跃欲试。真正要重构了,竟有点不知如何下手,于是总结了前四点。

 确定了重构的业务重点为流程设计之后,准备套一套自己想好的模式,确发现实难下手,一方面是因为原程序的结构已经整体成型,不好乱改,另一方面自己之前的设计也是花了大力气的,重新设计未见的就好。所以甚是郁闷,于是总结了后四条,重新定调,结合前四条,再改改看。

 郁闷了就要总结原因,思考原因: 为了实现设计模式而重构,以OO原则为指导,忽略了设计目标,所以整个思路就颠倒了。应该为了实现设计目标重构,以OO原则为指导,以模式为具体实现战术。

posted @ 2008-04-22 20:52 杨连国 阅读(9) | 评论 (0)编辑

2008年4月18日 #

 

1、  单一职责

一个类,只做好一件事,只有一个引起它变化的原因。

2、  开放封闭原则

软件实体应该是可扩展,而不可修改的。也就是对扩展开放、对修改封闭。

实现开放封闭的核心思想是对抽象编程,通过对抽象的继承和多态实现扩展。

拒绝滥用抽象,只将经常变化的部分进行抽象。

隔离变化,引入变化。

(数传软件)流程控制:状态模式、策略模式

3、  抽象倒置原则

依赖于抽象

高层模块不应该依赖于底层模块,二者都应该依赖于抽象。

抽象不应该依赖于具体,而具体应该依赖于抽象。

将变化的业务放到最外层(具体业务实现层)

4、  接口隔离原则

使用多个小的专门接口,而不是一个大的总接口

感觉就是接口的单一职责原则

5、  Liskov替换原则

子类能够替换基类

感觉作者理解的有偏差,这个原则应该是说子类和基类对方法语义的一致性。
posted @ 2008-04-18 11:05 杨连国 阅读(20) | 评论 (0)编辑

2008年4月15日 #

 

问题描述:

.net环境下,运行程序,第一切换到中文输入法时,中文输入功能灰掉(智能ABC)或者红叉叉(微软拼音),必须通过键盘Ctrl+Space 切换一下,后续才恢复正常。

 

解决方案:

既然是必须再切一次Ctrl+Space才能解决,那么为何不再软件内先行切一下,于是就在登录窗口内的TextBox获取焦点时,通过模拟键盘类SendKey,模拟发送两次Ctrl+Space,这个问题就解决了。

 

实现细节:

SendKey.Send(“^ ”);

 

关于如何实现空格模拟,通过MSDN获取的帮助是{BREAK},验证后,不对,然后上网查找,结论是,乱弹琴!自己动手测了一下

SendKey.Send(“^”+Convert.ToChar(32).ToString()),通过了,然后直接将转换操作用空格代替,也OK

posted @ 2008-04-15 17:46 杨连国 阅读(54) | 评论 (0)编辑