随笔 - 35  文章 - 10 评论 - 1040 trackbacks - 16

.net asp web c# vb VS2005 VS2008 VS2003

    姓名 景春雷
    网名 1-2-3
    生日 1980.2.29
    城市 沈阳
下午的聚会聊得很开心。照片:链接 6-7 20:01

与我联系

常用链接

我参与的团队

我的标签

随笔分类(36)

随笔档案(35)

文章分类(10)

相册

收藏夹(2)

积分与排名

  • 积分 - 125992
  • 排名 - 273

最新评论

阅读排行榜

评论排行榜

                    我们若不更加深邃,定将更加复杂。
                                                             ——史蒂芬 霍金

摘要


设计难题

我们将借用一个非常经典的例子:鸭子模拟程序[1]。如下图所示,我们的程序需要表现Mallard Duck(绿头野鸭)、Redhead Duck(红头野鸭)和Rubber Duck(橡皮鸭子)。由于Mallard Duck和Redhead Duck都有一个相同的函数“Fly()”,所以我们把这个函数提升到了父类Duck中。


由于Rubber Duck是Duck的子类,所以它自动获得了Fly()这个函数,但是橡皮鸭子是不能飞的!这是一个十分常见的设计问题。我们经常会把一些相关的类组织在一起,将它们共有的行为提升到父类中。这样做的好处不仅仅是少写了几行代码,更为重要的是,所有的子类拥有了相同的行为模式——对于新加入的开发者,他只要弄懂了父类,就对整个类层次了解了一大半;对于在程序的生命周期内需要不断增加子类的情况,这种设计就更加显得方便,因为我们不需要在每增加一个子类的时候都把所有的逻辑重新想一遍。
但是,讽刺的是,如果子类很多,就难免出现像Rubber Duck这样的另类;对于需要不断增加子类的情况,就更加的糟糕,我们在每一次将子类的行为提升到父类时都会恐惧将来会不会出现一个另类无法适用已经固定在父类中的逻辑……
下面将分别讨论可以解决此问题的4种不同的方法。

第1种设计:使用虚函数

最简单的方法就是把父类中的Fly()函数设为虚函数,并提供一个默认的实现。子类可以根据需要决定使用父类的默认实现或者重写一个新的实现,如下图所示。


嗯,这不是一个让人舒服的设计。那个Rubber Duck看起来就像一个转校生,它孤单寂寞,老师也不喜欢它。有时,你甚至会听到Duck、Mallard Duck和Redhead Duck在悄悄商量着:放学后我们去打电动游戏吧,嘘,别让Rubber Duck听到了……
如果需要不断的增加子类,情况就更加的糟糕。因为另类越来越多,我们只能尽力确保最初的那个小团体里的代码重用和逻辑一致。对于新来的,只能放任自流。
还有一个问题就是:对于那个不会飞的Rubber Duck,是否应该提供一个Fly()函数呢?有人赞成这样做,因为这样可以使用一个一致的接口来操作所有的Duck,例如可以这样写:
IList<Duck> ducks = new List<Duck>();
ducks.Add(
new MallardDuck());
ducks.Add(
new RedheadDuck());
ducks.Add(
new Rubber Duck());
foreach(Duck duck in ducks)
{
    duck.Fly();
}
也有人讨厌这样做。理由是,OO最吸引人的地方就是它是与人的思考方式最接近的编程方法。一个好的设计可以非常简洁自然,以至于不需要额外的文档加以说明。而Rubber Duck提供的Fly()函数无疑是一个例外情况,这样的例外越多,人们就越容易迷惑。就好像一个人的简历中明明写着“精通.net”,却既不懂C#,也不会VB,这么做只是为了和别人的简历一致,岂不是很奇怪?
那么,用窄接口会怎么样?
源代码下载:VirtualMethod.rar (VS2005控制台工程)
第2种设计:使用窄接口

如下图所示,在这个设计里提炼出了一个概念:Flyable。


请注意将一个概念显式地表现出来有多么的重要:现在我们都知道要实现哪些函数才算得上是“会飞”,并且知道Mallard Duck和Redhead Duck会飞,而Rubber Duck不会飞。
但是这个设计也有一个大缺点:Mallard Duck和Redhead Duck的Fly()是重复的代码,可是我们无法把它提升到Flyable接口中去,因为.net的接口里只允许定义抽象函数。与第1种设计相比,父类对子类控制的力度不够,增加子类也比较费事。
源代码下载:Interface.rar(VS2005控制台工程)
第3种设计:应用Strategy模式

这次,我们提炼出来的概念不再是“会不会飞(Flyable)”,而是“飞行的方式(FlyBehavior)”。


Duck的子类不再需要重复实现Fly()函数了,它们现在只需要指定一个适合自己的飞行方式就可以了。特别地,我们不再恐惧增加新的Duck的子类了——如果新的Duck的子类需要新的飞行方式,我们只要再增加一个FlyBehavior接口的实现类就可以了。这简直就是一个完美的设计!当然,看上去有些复杂,如果硬要挑出些毛病的话。
源代码下载: Strategy.rar(VS2005控制台工程)
第4种设计:使用Mixin

Mixin曾是Ruby等动态语言的独门秘技,没想到.net这个静态语言竟然也能学到八分像,还给取了个名叫Extension Methods。不过它绝不仅仅是“利用语法糖为无法修改源代码的类增加些函数”这么简单,它可以给我们更多的选择。还记得第2种设计(使用窄接口)的缺点么?没错,Mallard Duck和Redhead Duck的Fly()是重复的代码。Extension Methods正好可以用来解决这个问题。

注:我使用了一种比较形象的方法表示FlyModule和Flyable之间的关系,这并不是官方认可的UML表示法。
源代码下载: Mixin1.rar(VS2008控制台工程)
Strategy VS Mixin

现在来考虑为所有的Duck增加一个Quack()的功能。Mallard Duck和Redhead Duck可以嘎嘎叫(quack),而Rubber Duck只能吱吱叫(squeak)。Strategy和Mixin将如何应对这一需求变化呢?

1. Strategy



我们可以效仿FlyBehavior再添加一个QuackBehavior。虽然看上去需要作许多工作(添加了1个接口和2个类),但是可以注意到需要修改的代码非常少,大部分工作都是在新增代码(符合open-close原则);而且新增的代码只要按照原有的代码照猫画虎即可,基本不用怎么动脑子——这些正是优秀设计的特点。
源代码下载: Strategy2.rar(VS2005控制台工程)
2. Mixin



如上图所示,增加了Quackable和Squeakable两个接口,相当地简单直接。
源代码下载: Mixin2.rar(VS2008控制台工程)
Strategy和Mixin,你喜欢哪一个呢?

Strategy会使概念更集中一些——只要看一下Duck抽象类中都有哪些函数,对整个类层次就了解得差不多了;而且,FlyBehavior都有哪些实现方式也一目了然。统一的宽接口使得Client代码简单而一致。特别地,Strategy允许运行期动态更换FlyBehavior和QuackBehavior的实现,这是.net里的Mixin做不到的。

如果你是窄接口的拥护者,那么很可能会选Mixin了。不过看一下上面那个图,会有一种散和乱的感觉。Mixin更适合实现比较独立的概念,或是XXUtility这种东西。Mixin给人的感觉是简单、直接、轻巧。

Mixin 和 Template Method

Mixin非常适合用来实现“无论你是谁,只要提供了CompareTo()函数,就可以立即免费获得LessThan()、GreaterThan()、EqualTo()、LessEqual()和GreaterEqual() 这5个非常有用的函数”这样的语义。


Client代码:
IList<Duck> ducks = new List<Duck>();

MallardDuck duck1 
= new MallardDuck(1);
MallardDuck duck2 
= new MallardDuck(2);
MallardDuck duck3 
= new MallardDuck(2);

Console.WriteLine(
"duck1 <  duck2 ?   {0}", duck1.LessThan(duck2));
Console.WriteLine(
"duck1 >  duck2 ?   {0}", duck1.GreaterThan(duck2));
Console.WriteLine(
"duck1 <  duck3 ?   {0}", duck1.LessThan(duck3));
Console.WriteLine(
"duck2 <= duck3 ?   {0}", duck2.LessEqual(duck3));

源代码下载: Mixin3.rar(VS2008控制台工程)
以前,要想实现同样的语义只能用Abstract Class。


单以这个例子来看,使用Mixin有许多优点。首先,我们提炼出了一个明确、独立、通用的概念IComparable。其次,IComarable+CompareModule重用性更好。在这个例子中,由于IComarable+CompareModule里面的内容并不是领域相关的,所以就更应该将其提炼出来,这样Mallard Duck就可以集中精力处理领域相关的问题,程序的结构更加清晰,可读性更好。

不过上面那个例子并不能算得上是真正的Template Method模式。真正的Template Method模式是将所有子类共有的完成某项任务的算法提炼到父类中固定下来,子类只负责实现算法中的一个部分,不需要操心整个算法的所有步骤以及进行这些步骤的先后顺序和条件。就像说把大象装冰箱统共分三步,其实把猪装冰箱也统共分三步,把任何东西装冰箱都是分三步,这样我们就发现了一个可以提炼出来的算法“装冰箱”。

我们提炼出概念或算法不仅仅是为了“代码”重用——少些几行代码,仅仅是节省了一点儿体力而已。更为重要的是,我们想要节省脑力——对于已有代码,可以更快、更深刻地理解;如果需要新增代码,不需要把所有的逻辑再想一遍。

沏茶和泡咖啡的方法很相似,都要先烧水,然后把茶或速溶咖啡放入杯子,再倒满水。而且都需要考虑不少的细节,比如沏茶要用80度的水,泡咖啡用95度的最好;沏茶的话可以加两朵菊花,泡咖啡可以多加些糖;茶是可以直接加水续杯的,而咖啡不能续杯……如果有一天我想喝黑芝麻糊,难道必须再把所有的细节再想一遍?在现实生活中没有办法,但是在编程世界里就可以使用Template Method模式来逃避这烦人的工作。


活在编程世界是不是比现实世界要轻松些?

不过本文并不是想讲述Template Method的诸多好处,而是想问一个问题:用Mixin的方式代替Abstract Class实现Template Method模式是否更好?一个明显的好处是可以不受.net单继承的限制。

.net 只允许单继承,一个类只能继承一个Abstract Class,有时这的确挺不爽的。比如要实现一个“黑芝麻糊及刨冰一体机”,以前只能使用组合的方法。


而如果使用Mixin,就能使用多继承了。


等一等,多继承不是复杂与混乱之源么?是的,在C++中使用多继承确实存在许多陷阱和禁忌[3]。造成多继承复杂性的主要原因是属性和函数的模棱两可(ambiguity)问题——如果子类所继承的多个父类中有同名的函数或属性,当子类重载这些函数或Client代码想要调用这些函数时,编译器会不知道应当重载或调用哪个父类的函数。经过多年的实践和讨论,人们意识到必须对多继承做一些限制以减少造成混乱的可能性。.net中的Interface+显式实现接口正是这样一个简单而优雅的折中方案。现在,.net又稍稍放宽了限制,允许使用Interface+Extension Methods的方式多继承非虚函数,使得.net中的多继承功能更强大了一些。

Template VS Freedom

你会害怕使用继承么?你是否曾对Template Method模式充满恐惧?我曾经觉得把子类的核心算法提升到父类中固定下来是一个疯狂的想法。继承会造成子类对父类强烈的依赖,特别是使用Template Method模式的时候,子类将受到父类强大的束缚。我不喜欢束缚,我喜欢自由。特别是我还铭记着“组合优于继承”的设计原则。于是我分离出了一个又一个XxxUtility和Xxxxxxer,于是我的Abstract Class成了空壳。后来,我突然发现每次增加子类都要把所有的实现步骤和细节全部重新想上一遍同样十分恐怖,特别是有50多个子类等着你去添加的时候。继承使得子类对父类产生很强的依赖,这可以看作是缺点,但是也可以说,父类对子类有很强的“控制力”(特别是在使用Template Method模式的时候),使得子类简单而又整齐划一,就像同一个模子里铸出来的锡兵一样。幸运的是,继承和组合并不是一个非此即彼的抉择,我们可以在纪律和自由之间折中。把子类的核心算法提升到父类中形成Template Method模式,把其它的重复代码提炼出来形成Strategy或Mixin再使用之。不用害怕,没有人可以在一开始设计的时候就做出完美的提炼,除非他以前作过类似的程序或者是个不折不扣的先知。



参考文献

[1] Freeman et al, Head First Design Patterns. O’Reilly, 2004.
     影印版:深入浅出设计模式(英文影印版)。东南大学出版社,2005。

[2] Thomas et al, 孙勇等 译, Programming Ruby 中文版。电子工业出版社,2007.

[3] Meyers,侯捷 译, Effective C++ 中文版。华中科技大学出版社,2001. ——条款43:明智地运用多继承。


Tag标签: 模式
posted on 2008-02-26 08:16 1-2-3 阅读(4998) 评论(52)  编辑 收藏 所属分类: 模式

FeedBack:
#1楼  2008-02-26 08:27 寧愿為你       
学习了~!~~~ o(∩_∩)o...哈哈
  回复  引用  查看    
#2楼  2008-02-26 08:37 m [未注册用户]
博主LOGO很有意思。。。
  回复  引用  查看    
#3楼  2008-02-26 08:43 侯垒      
学习了.
  回复  引用  查看    
#4楼  2008-02-26 08:48 StillWartersRunDeep      
博主 认真的精神 赞一个
  回复  引用  查看    
#5楼  2008-02-26 08:50 jillzhang      
先收藏

  回复  引用  查看    
#6楼 [楼主] 2008-02-26 08:53 1-2-3      
@m
是在 http://logomaker.com/ 上面在线制作的,挺有意思的,我做了好几个呢,请看我的相册。
  回复  引用  查看    
#7楼 [楼主] 2008-02-26 08:54 1-2-3      
@jillzhang
早上好啊,老张。
  回复  引用  查看    
#8楼  2008-02-26 09:05 jillzhang      
@1-2-3
不是兄弟跟你熟,就夸你,写的真好,赞
我怎么就没有这水平呢,痛!:)
  回复  引用  查看    
#9楼  2008-02-26 09:05 floodpeak      
刚看前面以为作者是想用自己的方式复述一下Head first Design Pattern的第一章,后来发现作者又对其进行了扩展并进行了细致的分析,好~
  回复  引用  查看    
好像Head first Design Pattern的第一章,在办公室没有办法细看。
  回复  引用  查看    
#11楼  2008-02-26 09:11 pengyuan      
代码是c#3.0的?mixin1有点问题
  回复  引用  查看    
#12楼 [楼主] 2008-02-26 09:14 1-2-3      
@jillzhang

  回复  引用  查看    
#13楼 [楼主] 2008-02-26 09:16 1-2-3      
@pengyuan
对。最好用VS2008。
  回复  引用  查看    
#14楼  2008-02-26 09:20 jillzhang      
@1-2-3
你的logo是怎么下载的
要收费的,而且好贵,49美刀呀
我黏贴的代码
  回复  引用  查看    
#15楼  2008-02-26 09:22 pengyuan      
--引用--------------------------------------------------
1-2-3: @pengyuan
对。最好用VS2008。
--------------------------------------------------------

呵呵,才发现下载边上都会有个注明,vs 2008/5 控制台工程

赞一个,写得很不错
  回复  引用  查看    
#16楼  2008-02-26 09:23 pengyuan      
--引用--------------------------------------------------
jillzhang: @1-2-3
你的logo是怎么下载的
要收费的,而且好贵,49美刀呀
我黏贴的代码
--------------------------------------------------------

我想,是抓屏吧,嘿嘿
  回复  引用  查看    
#17楼  2008-02-26 09:29 Tristan(Guozhijian)      
head first design pattern?
o.o
  回复  引用  查看    
#18楼  2008-02-26 09:36 jillzhang      
@pengyuan
嘿嘿,我也整了一个
  回复  引用  查看    
#19楼  2008-02-26 09:40 winzheng      
不错,纳入吾囊中。。。
  回复  引用  查看    
#20楼 [楼主] 2008-02-26 09:43 1-2-3      
@jillzhang
--引用--------------------------------------------------
jillzhang: @1-2-3
你的logo是怎么下载的
要收费的,而且好贵,49美刀呀
我黏贴的代码
--------------------------------------------------------
下载确实是要钱的,我也是黏贴的代码。而且取得的是GIF图片,还很大。我是用FireWorks把它转成的PNG,又弄得小了一些,结果图片有点虚了。总之不是很爽。

  回复  引用  查看    
#21楼  2008-02-26 10:20 charry      
Design Patterns ,I like it!
  回复  引用  查看    
#22楼  2008-02-26 10:55 A1 [未注册用户]
good!
  回复  引用  查看    
#23楼  2008-02-26 11:29 绿蚂蚁      
very good~
  回复  引用  查看    
#24楼  2008-02-26 11:37 JackMa      
很好,收藏了!
  回复  引用  查看    
#25楼  2008-02-26 11:51 Anders Cui      
对你的文章风格赞一个!还学到了Mixin这个词。
说说我的想法:设计为Stategy时,Rubber Duck还是可以PerformFly(),至少它也有自己的“飞行方式”,但它有吗?没有。所以从Can-Do的角度来看还是Mixin更好点。
  回复  引用  查看    
#26楼 [楼主] 2008-02-26 11:59 1-2-3      
@Anders Cui
确实,到底是用宽接口还是窄接口的确是艰难的选择。
另:刚刚看到你的招聘贴,感觉你的公司很牛呀。
  回复  引用  查看    
#27楼  2008-02-26 12:04 Anders Cui      
@1-2-3
说实话,没有它说得那么好,呵呵
  回复  引用  查看    
#28楼 [楼主] 2008-02-26 12:10 1-2-3      
@Anders Cui

  回复  引用  查看    
其实往往复杂的东东,都会有一个很简单的解决方法。
  回复  引用  查看    
#30楼  2008-02-26 12:27 charly su [未注册用户]
呵呵,橡皮鸭应该要么属于玩具类或者橡皮物资(非生物)类的吧?不应该属于鸭子(生物)类的吧!所以这个设计图,一开始就是个错的啊
  回复  引用  查看    
#31楼  2008-02-26 13:23 2323 [未注册用户]
好文章,策略模式懂了
  回复  引用  查看    
#32楼 [楼主] 2008-02-26 13:39 1-2-3      
@charly su
也不能完全这么说。关键要看程序所面对的需求。从不同的视角,模塑出的“真实”的世界也不同。还要考虑模型如何被页面使用、被持久化以及性能是否符合要求还有实现起来是否方便等等。
以此例来说,如果是一个生物研究方面的程序,把橡皮鸭子作为Duck的子类就很古怪;但如果是一个模拟鸭子的游戏,单独弄一个橡皮物资类就有些笨拙了。
  回复  引用  查看    
#33楼  2008-02-26 14:25 billwillman      
已经收藏,并转载了。
好文章。
  回复  引用  查看    
#34楼  2008-02-26 15:55 怪怪      
精华, 精华 :)

抛砖引玉, 多说几个问题:

1. 现在越来越多的人意识到LZ说的这些问题了. 不过, 所有的ObjInterface.DoSomething(), 其实质都是 DoSomething(ObjInterface obj). 管它叫作Extend也好, Mixin也好, 实际上还是摆脱不了对接口的依赖. 尤其是面向对象的依赖方式, 往往是"厚重的胶合层"的罪魁祸首. 依赖于清晰定义的接口, 是四海一家的解决之道吗?

2. 有的时候, 只是接口的问题, 对于C#这样的, 可以用反射生成子类的方式, Mixin接口, 会比Extend更简洁和漂亮一些;未来还有Dynamic Interface. 可惜如果是添加功能实现, 也就只能Extend了, 因为反射生成子类的方式和Extend起到的效果是一样的. 另外, 鉴于1, Extend的局限性在于, 它并不能在内部改变对象的状态, 这使得它不过是更漂亮的自由函数而已.

3. 本质上是多主语的情况呢? 很多问题的实质是DoSomething(ObjInterface1 obj1, ObjInterface2 obj2), 面向对象语言对于这些情况, 表达能力实在是有限.

4. 关于本文, 还有一点需要考虑的, 就是Strategy和Extend, 很多时候, 其实并不是选这个更好还是选那个更好. Strategy达到的是运行期多态的目的, 而Extend则没有这方面的考虑: 代码一旦写下去, 运行期你不能再更换Extend出来的行为, 虽然可能有一些技巧可以应用, 但这不是本质上的.

个人认为, 在C#, Java上出现的这些问题, 实质上是面向对象的限制. 能用现有手段扩充的, 就用现有手段扩充; 其它的, 我看现在是需要一些新的思维来解决的时候了.
  回复  引用  查看    
不错,很详细,谢谢分享!
  回复  引用  查看    
#36楼  2008-02-26 16:29 球球      
好文章,以前还真没想过Extend可以这么用。
  回复  引用  查看    
#37楼  2008-02-26 16:47 deerchao      
@1-2-3
好文!
另,博主可否将RSS设置为全文输出?目前订阅的只有摘要.
  回复  引用  查看    
#38楼 [楼主] 2008-02-26 16:50 1-2-3      
@怪怪
谢谢。
> ObjInterface.DoSomething(), 其实质都是 DoSomething(ObjInterface obj).
没错,这就是所有静态语言实现OO功能的方法。而虚函数则是唯一的动态特性。
按说C#/Java的语法已经相当的简洁优雅了,可是还是会小小的羡慕下动态语言。人家可以在运行期随意增加、删除、改写对象的任何函数。接口?抽象类?虚函数?人家根本不需要。
为了编译期的类型检查和比较聪明的智能感知而付出的代价是否值得,也许是应该好好思考下了。
Dynamic Interface?我还是头一回听说,也许可以好好期待下。相信语言的动态特性会越来越受欢迎。
> Strategy达到的是运行期多态的目的, 而Extend则没有这方面的考虑
目前 Extentions Method 的能力确实还是挺弱的。不但没有动态特性,而且似乎只能扩展对象的普通实例方法。例如本文“Mixin 和 Template Method”节本来是想扩展“<、<=、 >、 >= 和 == ”操作符的,可是没搞定。
真希望.net胆子再大一点、步子再快一些^_^
  回复  引用  查看    
#39楼 [楼主] 2008-02-26 16:54 1-2-3      
@deerchao
谢谢您喜欢这篇文章。
请问怎么才能把RSS设置为全文输出呀?
  回复  引用  查看    
#40楼  2008-02-26 17:01 怪怪      
其实C#按照Strustroup的看法, 已经不是真正的静态语言了, 不过对反射等动态特性的使用, 确实还比较复杂. 我觉得按你的描述, 在.NET下你可能需要的是IronPython, 虽然我根本没用过Python -____-

智能感知是其次, 对于大规模代码来说"编译期的类型检查"我觉得还是挺必要的: 咱们现在用这些静态/半静态的语言, 所以不觉得, 比如我每次编译, 都有长长的出错列表的, 往往是一些特别小的手误; 很难想象以我现在的习惯, 使用动态语言挑起这些错会怎么样; 全靠Unit Test也不现实啊.

我指的新思路, 是一些和面向对象不同的编程范式和编程方式: 前者, 比如C++的Template, 比如FP中的一些解决之道; 后者, 就要求咱们在实际设计时自由发挥了. 拿.NET来说, 火药库也越来越足了, 其实咱们的空间还是挺大的.
  回复  引用  查看    
#41楼 [楼主] 2008-02-26 17:02 1-2-3      
@怪怪
Strategy 和 Mixin 似乎确实没有太大的可比性,不过想来想去只有“Strategy VS Mixin”这个题目会比较吸引眼球^_^

但是如果有一天Extention Method也能动态改变扩展的对象呢?呵呵,先不YY了。
  回复  引用  查看    
#42楼 [楼主] 2008-02-26 17:23 1-2-3      
@怪怪
C++的Template我只了解一点点(而且现在已经全忘光了)。感觉确实自成一套体系。不过似乎需要进行精心的设计并且有一定的适用范围。
  回复  引用  查看    
#43楼 [楼主] 2008-02-26 18:01 1-2-3      
@怪怪
.Net的火药确实不少了,甚至有些功能Java还没有呢
不过语言特性还是越多越好,库函数越丰富越好。正所谓屁股决定脑袋,如果语言没有某个特性,我们就压根不会往那方面想,想了也没用。所以语言会限制思维。说个笑话,我现在写文章都经常要压抑写“因为...所以”的冲动。还有如果第二段和第一段接不上,或者前一句和后一句有比较大的跳动,我的心里会空落落的,没底。
  回复  引用  查看    
#44楼  2008-02-26 23:30 deerchao      
@1-2-3
我问了dudu,他说设置为"摘要发布"的话,RSS里输出的也只有摘要,否则就是全文.

为了你精致的摘要图片,请无视我上次的请求吧 :(
  回复  引用  查看    
#45楼 [楼主] 2008-02-27 08:01 1-2-3      
@deerchao
呵呵,原来如此。
  回复  引用  查看    
#46楼  2008-02-27 09:54 极地银狐.NET      
楼主写的不错,不过我总觉得.橡皮鸭子不能从鸭子继承,因为它压根不是鸭子,只是一个外形像鸭子的橡皮,可以说只实现了一般鸭子的一部分外部特性
  回复  引用  查看    
#47楼  2008-02-28 09:46 Clark Zheng      
前途无量呀
  回复  引用  查看    
#48楼 [楼主] 2008-02-29 07:59 1-2-3      
@Clark Zheng
啥前途呀,兴许过两年就转了。
  回复  引用  查看    
#49楼  2008-02-29 14:42 MS的明天      
学习到了不少
  回复  引用  查看    
#50楼  2008-03-03 14:14 ∈鱼杆      
很好很经典
  回复  引用  查看    
#51楼  2008-03-03 14:15 ∈鱼杆      
收藏一下,不错!
  回复  引用  查看    

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
 
另存  打印
最新IT新闻:
· 微软高管:Wii用户最终会成为Xbox 360用户
· 遵守YouTube案裁定 谷歌将陷入隐私指控深渊
· iPhone入华在即 中国手机产业生存面临考验
· 阿里巴巴集团再向淘宝注资20亿元
· 56被关一月 危机的是整个视频业