Wind-Eagle

No pain,no gain!
posts - 67, comments - 53, trackbacks - 0, articles - 4
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2008年5月4日

何谓控制反转(IoC = Inversion of Control),何谓依赖注入(DI = Dependency Injection)?

  对于初次接触这些概念的初学者,不免会一头雾水。正如笔者第一次看到这些名词一样,一阵窘迫……IT界不亏是哄抢眼球的行业,每个新出现的语汇都如此迷离。好在我们也同时拥有Internet这个最广博的信息来源。

  IoC,用白话来讲,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。
正在业界为IoC争吵不休时,大师级人物Martin Fowler也站出来发话,以一篇经典文章《Inversion of Control Containers and the Dependency Injection pattern》为IoC正名,至此,IoC又获得了一个新的名字:“依赖注入 (Dependency Injection)”。

  相对IoC 而言,“依赖注入”的确更加准确的描述了这种古老而又时兴的设计理念。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

  举一个很简单的例子:IBM T40笔记本电脑一台、USB硬盘和U盘各一只。笔记本电脑与外围存储设备通过预先指定的一个接口(USB)相连,对于笔记本而言,只是将用户指定的数据发送到USB接口,而这些数据何去何从,则由当前接入的USB设备决定。在USB设备加载之前,笔记本不可能预料用户将在USB接口上接入何种设备,只有USB设备接入之后,这种设备之间的依赖关系才开始形成。

  对应上面关于依赖注入机制的描述,在运行时(系统开机,USB 设备加载)由容器(运行在笔记本中的Windows操作系统)将依赖关系(笔记本依赖USB设备进行数据存取)注入到组件中(Windows文件访问组件)。
这就是依赖注入模式在现实世界中的一个版本。

posted @ 2008-05-04 22:44 Andrew Yin 阅读(187) | 评论 (1)编辑 编辑

{关键字}

测试驱动开发/Test Driven Development/TDD
测试用例/TestCase/TC
设计/Design
重构/Refactoring

{TDD的目标}

Clean Code That Works

这句话的含义是,事实上我们只做两件事情:让代码奏效(Work)和让代码洁净(Clean),前者是把事情做对,后者是把事情做好。想想看,其实我们平时所做的所有工作,除去无用的工作和错误的工作以外,真正正确的工作,并且是真正有意义的工作,其实也就只有两大类:增加功能和提升设计,而TDD正是在这个原则上产生的。如果您的工作并非我们想象的这样,(这意味着您还存在第三类正确有意义的工作,或者您所要做的根本和我们在说的是两回事),那么这告诉我们您并不需要TDD,或者不适用TDD。而如果我们偶然猜对(这对于我来说是偶然,而对于Kent Beck和Martin Fowler这样的大师来说则是辛勤工作的成果),那么恭喜您,TDD有可能成为您显著提升工作效率的一件法宝。请不要将信将疑,若即若离,因为任何一项新的技术——只要是从根本上改变人的行为方式的技术——就必然使得相信它的人越来越相信,不信的人越来越不信。这就好比学游泳,唯一能学会游泳的途径就是亲自下去游,除此之外别无他法。这也好比成功学,即使把卡耐基或希尔博士的书倒背如流也不能拥有积极的心态,可当你以积极的心态去成就了一番事业之后,你就再也离不开它了。相信我,TDD也是这样!想试用TDD的人们,请遵循下面的步骤:

编写TestCase --> 实现TestCase --> 重构
(确定范围和目标)   (增加功能)   (提升设计)

[友情提示:敏捷建模中的一个相当重要的实践被称为:Prove it With Code,这种想法和TDD不谋而合。]

{TDD的优点}

    『充满吸引力的优点』

  1. 完工时完工。表明我可以很清楚的看到自己的这段工作已经结束了,而传统的方式很难知道什么时候编码工作结束了。
  2. 全面正确的认识代码和利用代码,而传统的方式没有这个机会。
  3. 为利用你成果的人提供Sample,无论它是要利用你的源代码,还是直接重用你提供的组件。
  4. 开发小组间降低了交流成本,提高了相互信赖程度。
  5. 避免了过渡设计。
  6. 系统可以与详尽的测试集一起发布,从而对程序的将来版本的修改和扩展提供方便。
  7. TDD给了我们自信,让我们今天的问题今天解决,明天的问题明天解决,今天不能解决明天的问题,因为明天的问题还没有出现(没有TestCase),除非有TestCase否则我决不写任何代码;明天也不必担心今天的问题,只要我亮了绿灯。

     

    『不显而易见的优点』

  8. 逃避了设计角色。对于一个敏捷的开发小组,每个人都在做设计。
  9. 大部分时间代码处在高质量状态,100%的时间里成果是可见的。
  10. 由于可以保证编写测试和编写代码的是相同的程序员,降低了理解代码所花费的成本。
  11. 为减少文档和代码之间存在的细微的差别和由这种差别所引入的Bug作出杰出贡献。
  12. 在预先设计和紧急设计之间建立一种平衡点,为你区分哪些设计该事先做、哪些设计该迭代时做提供了一个可靠的判断依据。

    『有争议的优点』

  13. 事实上提高了开发效率。每一个正在使用TDD并相信TDD的人都会相信这一点,但观望者则不同,不相信TDD的人甚至坚决反对这一点,这很正常,世界总是这样。
  14. 发现比传统测试方式更多的Bug。
  15. 使IDE的调试功能失去意义,或者应该说,避免了令人头痛的调试和节约了调试的时间。
  16. 总是处在要么编程要么重构的状态下,不会使人抓狂。(两顶帽子)
  17. 单元测试非常有趣。

 

{TDD的步骤}
编写TestCase --> 实现TestCase --> 重构
(不可运行)   (可运行)   (重构)
步骤 制品
(1)快速新增一个测试用例 新的TestCase
(2)编译所有代码,刚刚写的那个测试很可能编译不通过 原始的TODO List
(3)做尽可能少的改动,让编译通过 Interface
(4)运行所有的测试,发现最新的测试不能编译通过 -(Red Bar)
(5)做尽可能少的改动,让测试通过 Implementation
(6)运行所有的测试,保证每个都能通过 -(Green Bar)
(7)重构代码,以消除重复设计 Clean Code That Works

{FAQ}

[什么时候重构?]
如果您在软件公司工作,就意味着您成天都会和想通过重构改善代码质量的想法打交道,不仅您如此,您的大部分同事也都如此。可是,究竟什么时候该重构,什么情况下应该重构呢?我相信您和您的同事可能有很多不同的看法,最常见的答案是“该重构时重构”,“写不下去的时候重构”,和“下一次迭代开始之前重构”,或者干脆就是“最近没时间,就不重构了,下次有时间的时候重构吧”。正如您已经预见到我想说的——这些想法都是对重构的误解。重构不是一种构建软件的工具,不是一种设计软件的模式,也不是一个软件开发过程中的环节,正确理解重构的人应该把重构看成一种书写代码的方式,或习惯,重构时时刻刻有可能发生。在TDD中,除去编写测试用例和实现测试用例之外的所有工作都是重构,所以,没有重构任何设计都不能实现。至于什么时候重构嘛,还要分开看,有三句话是我的经验:实现测试用例时重构代码,完成某个特性时重构设计,产品的重构完成后还要记得重构一下测试用例哦。

[什么时候设计?]
这个问题比前面一个要难回答的多,实话实说,本人在依照TDD开发软件的时候也常常被这个问题困扰,总是觉得有些问题应该在写测试用例之前定下来,而有些问题应该在新增一个一个测试用例的过程中自然出现,水到渠成。所以,我的建议是,设计的时机应该由开发者自己把握,不要受到TDD方式的限制,但是,不需要事先确定的事一定不能事先确定,免得捆住了自己的手脚。

[什么时候增加新的TestCase?]
没事做的时候。通常我们认为,如果你要增加一个新的功能,那么先写一个不能通过的TestCase;如果你发现了一个bug,那么先写一个不能通过的TestCase;如果你现在什么都没有,从0开始,请先写一个不能通过的TestCase。所有的工作都是从一个TestCase开始。此外,还要注意的是,一些大师要求我们每次只允许有一个TestCase亮红灯,在这个TestCase没有Green之前不可以写别的TestCase,这种要求可以适当考虑,但即使有多个TestCase亮红灯也不要紧,并未违反TDD的主要精神。

[TestCase该怎么写?]
测试用例的编写实际上就是两个过程:使用尚不存在的代码和定义这些代码的执行结果。所以一个TestCase也就应该包括两个部分——场景和断言。第一次写TestCase的人会有很大的不适应的感觉,因为你之前所写的所有东西都是在解决问题,现在要你提出问题确实不大习惯,不过不用担心,你正在做正确的事情,而这个世界上最难的事情也不在于如何解决问题,而在于ask the right question!

[TDD能帮助我消除Bug吗?]
答:不能!千万不要把“测试”和“除虫”混为一谈!“除虫”是指程序员通过自己的努力来减少bug的数量(消除bug这样的字眼我们还是不要讲为好^_^),而“测试”是指程序员书写产品以外的一段代码来确保产品能有效工作。虽然TDD所编写的测试用例在一定程度上为寻找bug提供了依据,但事实上,按照TDD的方式进行的软件开发是不可能通过TDD再找到bug的(想想我们前面说的“完工时完工”),你想啊,当我们的代码完成的时候,所有的测试用例都亮了绿灯,这时隐藏在代码中的bug一个都不会露出马脚来。

但是,如果要问“测试”和“除虫”之间有什么联系,我相信还是有很多话可以讲的,比如TDD事实上减少了bug的数量,把查找bug战役的关注点从全线战场提升到代码战场以上。还有,bug的最可怕之处不在于隐藏之深,而在于满天遍野。如果你发现了一个用户很不容易才能发现的bug,那么不一定对工作做出了什么杰出贡献,但是如果你发现一段代码中,bug的密度或离散程度过高,那么恭喜你,你应该抛弃并重写这段代码了。TDD避免了这种情况,所以将寻找bug的工作降低到了一个新的低度。

[我该为一个Feature编写TestCase还是为一个类编写TestCase?]
初学者常问的问题。虽然我们从TDD的说明书上看到应该为一个特性编写相应的TestCase,但为什么著名的TDD大师所写的TestCase都是和类/方法一一对应的呢?为了解释这个问题,我和我的同事们都做了很多试验,最后我们得到了一个结论,虽然我不知道是否正确,但是如果您没有答案,可以姑且相信我们。

我们的研究结果表明,通常在一个特性的开发开始时,我们针对特性编写测试用例,如果您发现这个特性无法用TestCase表达,那么请将这个特性细分,直至您可以为手上的特性写出TestCase为止。从这里开始是最安全的,它不会导致任何设计上重大的失误。但是,随着您不断的重构代码,不断的重构TestCase,不断的依据TDD的思想做下去,最后当产品伴随测试用例集一起发布的时候,您就会不经意的发现经过重构以后的测试用例很可能是和产品中的类/方法一一对应的。

[什么时候应该将全部测试都运行一遍?]
Good Question!大师们要求我们每次重构之后都要完整的运行一遍测试用例。这个要求可以理解,因为重构很可能会改变整个代码的结构或设计,从而导致不可预见的后果,但是如果我正在开发的是一个ERP怎么办?运行一遍完整的测试用例可能将花费数个小时,况且现在很多重构都是由工具做到的,这个要求的可行性和前提条件都有所动摇。所以我认为原则上你可以挑几个你觉得可能受到本次重构影响的TestCase去run,但是如果运行整个测试包只要花费数秒的时间,那么不介意你按大师的要求去做。

[什么时候改进一个TestCase?]
增加的测试用例或重构以后的代码导致了原来的TestCase的失去了效果,变得无意义,甚至可能导致错误的结果,这时是改进TestCase的最好时机。但是有时你会发现,这样做仅仅导致了原来的TestCase在设计上是臃肿的,或者是冗余的,这都不要紧,只要它没有失效,你仍然不用去改进它。记住,TestCase不是你的产品,它不要好看,也不要怎么太科学,甚至没有性能要求,它只要能完成它的使命就可以了——这也证明了我们后面所说的“用Ctrl-C/Ctrl-V编写测试用例”的可行性。

但是,美国人的想法其实跟我们还是不太一样,拿托尼巴赞的MindMap来说吧,其实画MindMap只是为了表现自己的思路,或记忆某些重要的事情,但托尼却建议大家把MindMap画成一件艺术品,甚至还有很多艺术家把自己画的抽象派MindMap拿出来帮助托尼做宣传。同样,大师们也要求我们把TestCase写的跟代码一样质量精良,可我想说的是,现在国内有几个公司能把产品的代码写的精良??还是一步一步慢慢来吧。

[为什么原来通过的测试用例现在不能通过了?]
这是一个警报,Red Alert!它可能表达了两层意思——都不是什么好意思——1)你刚刚进行的重构可能失败了,或存在一些错误未被发现,至少重构的结果和原来的代码不等价了。2)你刚刚增加的TestCase所表达的意思跟前面已经有的TestCase相冲突,也就是说,新增的功能违背了已有的设计,这种情况大部分可能是之前的设计错了。但无论哪错了,无论是那层意思,想找到这个问题的根源都比TDD的正常工作要难。

[我怎么知道那里该有一个方法还是该有一个类?]
这个问题也是常常出现在我的脑海中,无论你是第一次接触TDD或者已经成为TDD专家,这个问题都会缠绕着你不放。不过问题的答案可以参考前面的“什么时候设计”一节,答案不是唯一的。其实多数时候你不必考虑未来,今天只做今天的事,只要有重构工具,从方法到类和从类到方法都很容易。

[我要写一个TestCase,可是不知道从哪里开始?]
从最重要的事开始,what matters most?从脚下开始,从手头上的工作开始,从眼前的事开始。从一个没有UI的核心特性开始,从算法开始,或者从最有可能耽误时间的模块开始,从一个最严重的bug开始。这是TDD主义者和鼠目寸光者的一个共同点,不同点是前者早已成竹在胸。

[为什么我的测试总是看起来有点愚蠢?]
哦?是吗?来,握个手,我的也是!不必担心这一点,事实上,大师们给的例子也相当愚蠢,比如一个极端的例子是要写一个两个int变量相加的方法,大师先断言2+3=5,再断言5+5=10,难道这些代码不是很愚蠢吗?其实这只是一个极端的例子,当你初次接触TDD时,写这样的代码没什么不好,以后当你熟练时就会发现这样写没必要了,要记住,谦虚是通往TDD的必经之路!从经典开发方法转向TDD就像从面向过程转向面向对象一样困难,你可能什么都懂,但你写出来的类没有一个纯OO的!我的同事还告诉我真正的太极拳,其速度是很快的,不比任何一个快拳要慢,但是初学者(通常是指学习太极拳的前10年)太不容易把每个姿势都做对,所以只能慢慢来。

[什么场合不适用TDD?]
问的好,确实有很多场合不适合使用TDD。比如对软件质量要求极高的军事或科研产品——神州六号,人命关天的软件——医疗设备,等等,再比如设计很重要必须提前做好的软件,这些都不适合TDD,但是不适合TDD不代表不能写TestCase,只是作用不同,地位不同罢了。

{Best Practise}

[微笑面对编译错误]
学生时代最害怕的就是编译错误,编译错误可能会被老师视为上课不认真听课的证据,或者同学间相互嘲笑的砝码。甚至离开学校很多年的老程序员依然害怕它就像害怕迟到一样,潜意识里似乎编译错误极有可能和工资挂钩(或者和智商挂钩,反正都不是什么好事)。其实,只要提交到版本管理的代码没有编译错误就可以了,不要担心自己手上的代码的编译错误,通常,编译错误都集中在下面三个方面:
(1)你的代码存在低级错误
(2)由于某些Interface的实现尚不存在,所以被测试代码无法编译
(3)由于某些代码尚不存在,所以测试代码无法编译
请注意第二点与第三点完全不同,前者表明设计已存在,而实现不存在导致的编译错误;后者则指仅有TestCase而其它什么都没有的情况,设计和实现都不存在,没有Interface也没有Implementation。

另外,编译器还有一个优点,那就是以最敏捷的身手告诉你,你的代码中有那些错误。当然如果你拥有Eclipse这样可以及时提示编译错误的IDE,就不需要这样的功能了。

[重视你的计划清单]
在非TDD的情况下,尤其是传统的瀑布模型的情况下,程序员不会不知道该做什么,事实上,总是有设计或者别的什么制品在引导程序员开发。但是在TDD的情况下,这种优势没有了,所以一个计划清单对你来说十分重要,因为你必须自己发现该做什么。不同性格的人对于这一点会有不同的反应,我相信平时做事没什么计划要依靠别人安排的人(所谓将才)可能略有不适应,不过不要紧,Tasks和Calendar(又称效率手册)早已成为现代上班族的必备工具了;而平时工作生活就很有计划性的人,比如我:),就会更喜欢这种自己可以掌控Plan的方式了。

[废黜每日代码质量检查]
如果我没有记错的话,PSP对于个人代码检查的要求是蛮严格的,而同样是在针对个人的问题上,TDD却建议你废黜每日代码质量检查,别起疑心,因为你总是在做TestCase要求你做的事情,并且总是有办法(自动的)检查代码有没有做到这些事情——红灯停绿灯行,所以每日代码检查的时间可能被节省,对于一个严格的PSP实践者来说,这个成本还是很可观的!

此外,对于每日代码质量检查的另一个好处,就是帮助你认识自己的代码,全面的从宏观、微观、各个角度审视自己的成果,现在,当你依照TDD做事时,这个优点也不需要了,还记得前面说的TDD的第二个优点吗,因为你已经全面的使用了一遍你的代码,这完全可以达到目的。

但是,问题往往也并不那么简单,现在有没有人能告诉我,我如何全面审视我所写的测试用例呢?别忘了,它们也是以代码的形式存在的哦。呵呵,但愿这个问题没有把你吓到,因为我相信到目前为止,它还不是瓶颈问题,况且在编写产品代码的时候你还是会自主的发现很多测试代码上的没考虑到的地方,可以就此修改一下。道理就是如此,世界上没有任何方法能代替你思考的过程,所以也没有任何方法能阻止你犯错误,TDD仅能让你更容易发现这些错误而已。

[如果无法完成一个大的测试,就从最小的开始]
如果我无法开始怎么办,教科书上有个很好的例子:我要写一个电影列表的类,我不知道如何下手,如何写测试用例,不要紧,首先想象静态的结果,如果我的电影列表刚刚建立呢,那么它应该是空的,OK,就写这个断言吧,断言一个刚刚初始化的电影列表是空的。这不是愚蠢,这是细节,奥运会五项全能的金牌得主玛丽莲·金是这样说的:“成功人士的共同点在于……如果目标不够清晰,他们会首先做通往成功道路上的每一个细小步骤……”。

[尝试编写自己的xUnit]
Kent Beck建议大家每当接触一个新的语言或开发平台的时候,就自己写这个语言或平台的xUnit,其实几乎所有常用的语言和平台都已经有了自己的xUnit,而且都是大同小异,但是为什么大师给出了这样的建议呢。其实Kent Beck的意思是说通过这样的方式你可以很快的了解这个语言或平台的特性,而且xUnit确实很简单,只要知道原理很快就能写出来。这对于那些喜欢自己写底层代码的人,或者喜欢控制力的人而言是个好消息。

[善于使用Ctrl-C/Ctrl-V来编写TestCase]
不必担心TestCase会有代码冗余的问题,让它冗余好了。

[永远都是功能First,改进可以稍后进行]
上面这个标题还可以改成另外一句话:避免过渡设计!

[淘汰陈旧的用例]
舍不得孩子套不着狼。不要可惜陈旧的用例,因为它们可能从概念上已经是错误的了,或仅仅会得出错误的结果,或者在某次重构之后失去了意义。当然也不一定非要删除它们,从TestSuite中除去(JUnit)或加上Ignored(NUnit)标签也是一个好办法。

[用TestCase做试验]
如果你在开始某个特性或产品的开发之前对某个领域不太熟悉或一无所知,或者对自己在该领域里的能力一无所知,那么你一定会选择做试验,在有单元测试作工具的情况下,建议你用TestCase做试验,这看起来就像你在写一个验证功能是否实现的TestCase一样,而事实上也一样,只不过你所验证的不是代码本身,而是这些代码所依赖的环境。

[TestCase之间应该尽量独立]
保证单独运行一个TestCase是有意义的。

[不仅测试必须要通过的代码,还要测试必须不能通过的代码]
这是一个小技巧,也是不同于设计思路的东西。像越界的值或者乱码,或者类型不符的变量,这些输入都可能会导致某个异常的抛出,或者导致一个标示“illegal parameters”的返回值,这两种情况你都应该测试。当然我们无法枚举所有错误的输入或外部环境,这就像我们无法枚举所有正确的输入和外部环境一样,只要TestCase能说明问题就可以了。

[编写代码的第一步,是在TestCase中用Ctrl-C]
这是一个高级技巧,呃,是的,我是这个意思,我不是说这个技巧难以掌握,而是说这个技巧当且仅当你已经是一个TDD高手时,你才能体会到它的魅力。多次使用TDD的人都有这样的体会,既然我的TestCase已经写的很好了,很能说明问题,为什么我的代码不能从TestCase拷贝一些东西来呢。当然,这要求你的TestCase已经具有很好的表达能力,比如断言f(5)=125的方式显然没有断言f(5)=5^(5-2)表达更多的内容。

[测试用例包应该尽量设计成可以自动运行的]
如果产品是需要交付源代码的,那我们应该允许用户对代码进行修改或扩充后在自己的环境下run整个测试用例包。既然通常情况下的产品是可以自动运行的,那为什么同样作为交付用户的制品,测试用例包就不是自动运行的呢?即使产品不需要交付源代码,测试用例包也应该设计成可以自动运行的,这为测试部门或下一版本的开发人员提供了极大的便利。

[只亮一盏红灯]
大师的建议,前面已经提到了,仅仅是建议。

[用TestCase描述你发现的bug]
如果你在另一个部门的同事使用了你的代码,并且,他发现了一个bug,你猜他会怎么做?他会立即走到你的工位边上,大声斥责说:“你有bug!”吗?如果他胆敢这样对你,对不起,你一定要冷静下来,不要当面回骂他,相反你可以微微一笑,然后心平气和的对他说:“哦,是吗?那么好吧,给我一个TestCase证明一下。”现在局势已经倒向你这一边了,如果他还没有准备好回答你这致命的一击,我猜他会感到非常羞愧,并在内心责怪自己太莽撞。事实上,如果他的TestCase没有过多的要求你的代码(而是按你们事前的契约),并且亮了红灯,那么就可以确定是你的bug,反之,对方则无理了。用TestCase描述bug的另一个好处是,不会因为以后的修改而再次暴露这个bug,它已经成为你发布每一个版本之前所必须检查的内容了。

{关于单元测试}

单元测试的目标是

Keep the bar green to keep the code clean

这句话的含义是,事实上我们只做两件事情:让代码奏效(Keep the bar green)和让代码洁净(Keep the code clean),前者是把事情做对,后者是把事情做好,两者既是TDD中的两顶帽子,又是xUnit架构中的因果关系。

单元测试作为软件测试的一个类别,并非是xUnit架构创造的,而是很早就有了。但是xUnit架构使得单元测试变得直接、简单、高效和规范,这也是单元测试最近几年飞速发展成为衡量一个开发工具和环境的主要指标之一的原因。正如Martin Fowler所说:“软件工程有史以来从没有如此众多的人大大收益于如此简单的代码!”而且多数语言和平台的xUnit架构都是大同小异,有的仅是语言不同,其中最有代表性的是JUnit和NUnit,后者是前者的创新和扩展。一个单元测试框架xUnit应该:1)使每个TestCase独立运行;2)使每个TestCase可以独立检测和报告错误;3)易于在每次运行之前选择TestCase。下面是我枚举出的xUnit框架的概念,这些概念构成了当前业界单元测试理论和工具的核心:

[测试方法/TestMethod]
测试的最小单位,直接表示为代码。

[测试用例/TestCase]
由多个测试方法组成,是一个完整的对象,是很多TestRunner执行的最小单位。

[测试容器/TestSuite]
由多个测试用例构成,意在把相同含义的测试用例手动安排在一起,TestSuite可以呈树状结构因而便于管理。在实现时,TestSuite形式上往往也是一个TestCase或TestFixture。

[断言/Assertion]
断言一般有三类,分别是比较断言(如assertEquals),条件断言(如isTrue),和断言工具(如fail)。

[测试设备/TestFixture]
为每个测试用例安排一个SetUp方法和一个TearDown方法,前者用于在执行该测试用例或该用例中的每个测试方法前调用以初始化某些内容,后者在执行该测试用例或该用例中的每个方法之后调用,通常用来消除测试对系统所做的修改。

[期望异常/Expected Exception]
期望该测试方法抛出某种指定的异常,作为一个“断言”内容,同时也防止因为合情合理的异常而意外的终止了测试过程。

[种类/Category]
为测试用例分类,实际使用时一般有TestSuite就不再使用Category,有Category就不再使用TestSuite。

[忽略/Ignored]
设定该测试用例或测试方法被忽略,也就是不执行的意思。有些被抛弃的TestCase不愿删除,可以定为Ignored。

[测试执行器/TestRunner]
执行测试的工具,表示以何种方式执行测试,别误会,这可不是在代码中规定的,完全是与测试内容无关的行为。比如文本方式,AWT方式,swing方式,或者Eclipse的一个视图等等。

{实例:Fibonacci数列}

下面的Sample展示TDDer是如何编写一个旨在产生Fibonacci数列的方法。
(1)首先写一个TC,断言fib(1) = 1;fib(2) = 1;这表示该数列的第一个元素和第二个元素都是1。

public void testFab() {
        assertEquals(
1, fib(1));
        assertEquals(
1, fib(2));
}

(2)上面这段代码不能编译通过,Great!——是的,我是说Great!当然,如果你正在用的是Eclipse那你不需要编译,Eclipse会告诉你不存在fib方法,单击mark会问你要不要新建一个fib方法,Oh,当然!为了让上面那个TC能通过,我们这样写:

public int fib( int n ) {
        
return 1;
}

(3)现在那个TC亮了绿灯,wow!应该庆祝一下了。接下来要增加TC的难度了,测第三个元素。

public void testFab() {
        assertEquals(
1, fib(1));
        assertEquals(
1, fib(2));
        assertEquals(
2, fib(3));
}

不过这样写还不太好看,不如这样写:

public void testFab() {
        assertEquals(
1, fib(1));
        assertEquals(
1, fib(2));
        assertEquals(fib(
1)+fib(2), fib(3));
}

(4)新增加的断言导致了红灯,为了扭转这一局势我们这样修改fib方法,其中部分代码是从上面的代码中Ctrl-C/Ctrl-V来的:

public int fib( int n ) {
        
if ( n == 3 ) return fib(1)+fib(2);
        
return 1;
}

(5)天哪,这真是个贱人写的代码!是啊,不是吗?因为TC就是产品的蓝本,产品只要恰好满足TC就ok。所以事情发展到这个地步不是fib方法的错,而是TC的错,于是TC还要进一步要求:

public void testFab() {
        assertEquals(
1, fib(1));
        assertEquals(
1, fib(2));
        assertEquals(fib(
1)+fib(2), fib(3));
        assertEquals(fib(
2)+fib(3), fib(4));
}

(6)上有政策下有对策。

public int fib( int n ) {
        
if ( n == 3 ) return fib(1)+fib(2);
        
if ( n == 4 ) return fib(2)+fib(3);
        
return 1;
}

(7)好了,不玩了。现在已经不是贱不贱的问题了,现在的问题是代码出现了冗余,所以我们要做的是——重构:

public int fib( int n ) {
        
if ( n == 1 || n == 2 ) return 1;
        
else return fib( n - 1 ) + fib( n - 2 );
}

(8)好,现在你已经fib方法已经写完了吗?错了,一个危险的错误,你忘了错误的输入了。我们令0表示Fibonacci中没有这一项。

public void testFab() {
        assertEquals(
1, fib(1));
        assertEquals(
1, fib(2));
        assertEquals(fib(
1)+fib(2), fib(3));
        assertEquals(fib(
2)+fib(3), fib(4));
        assertEquals(
0, fib(0));
        assertEquals(
0, fib(-1));
}

then change the method fib to make the bar grean:

public int fib( int n ) {
        
if ( n <= 0 ) return 0;
        
if ( n == 1 || n == 2 ) return 1;
        
else return fib( n - 1 ) + fib( n - 2 );
}

(9)下班前最后一件事情,把TC也重构一下:

public void testFab() {
        
int cases[][] = {
                {
00}, {-10},  //the wrong parameters
                {11}, {21}};  //the first 2 elements

        
for (int i = 0; i < cases.length; i++)
                assertEquals( cases[i][
1], fib(cases[i][0]) );

        
//the rest elements
        for (int i = 3; i < 20; i++)
                assertEquals(fib(i
-1)+fib(i-2), fib(i));
}

(10)打完收工。

{关于本文的写作}

在本文的写作过程中,作者也用到了TDD的思维,事实上作者先构思要写一篇什么样的文章,然后写出这篇文章应该满足的几个要求,包括功能的要求(要写些什么)和性能的要求(可读性如何)和质量的要求(文字的要求),这些要求起初是一个也达不到的(因为正文还一个字没有),在这种情况下作者的文章无法编译通过,为了达到这些要求,作者不停的写啊写啊,终于在花尽了两个月的心血之后完成了当初既定的所有要求(make the bar green),随后作者整理了一下文章的结构(重构),在满意的提交给了Blog系统之后,作者穿上了一件绿色的汗衫,趴在地上,学了两声青蛙叫。。。。。。。^_^

{后记:Martin Fowler在中国}

从本文正式完成到发表的几个小时里,我偶然读到了Martin Fowler先生北京访谈录,其间提到了很多对测试驱动开发的看法,摘抄在此:

Martin Fowler:当然(值得花一半的时间来写单元测试)!因为单元测试能够使你更快的完成工作。无数次的实践已经证明这一点。你的时间越是紧张,就越要写单元测试,它看上去慢,但实际上能够帮助你更快、更舒服地达到目的。
Martin Fowler:什么叫重要?什么叫不重要?这是需要逐渐认识的,不是想当然的。我为绝大多数的模块写单元测试,是有点烦人,但是当你意识到这工作的价值时,你会欣然的。
Martin Fowler:对全世界的程序员我都是那么几条建议:……第二,学习测试驱动开发,这种新的方法会改变你对于软件开发的看法。……

——《程序员》,2005年7月刊

{鸣谢}

fhawk
Dennis Chen
般若菩提
Kent Beck
Martin Fowler
c2.com

(转载本文需注明出处:Brian Sun @ 爬树的泡泡[http://www.blogjava.net/briansun])

posted @ 2008-05-04 22:34 Andrew Yin 阅读(43) | 评论 (0)编辑 编辑

在unicode 字符串中,中文的范围是在4E00..9FFF:CJK Unified Ideographs。

通过对字符的unicode编码进行判断来确定字符是否为中文。

protected bool   IsChineseLetter(string input,int index)
     ...
...{
        
int code = 0;
        
int chfrom = Convert.ToInt32("4e00", 16);    //范围(0x4e00~0x9fff)转换成int(chfrom~chend)
        int chend = Convert.ToInt32("9fff", 16);
        
if (input != "")
         ...
...{
             code
= Char.ConvertToUtf32(input, index);    //获得字符串input中指定索引index处字符unicode编码
            
           
if (code >= chfrom && code <= chend)     
             ...
...{
                
return true;     //当code在中文范围内返回true

             }

            
else
             ...
...{
                 
return false ;    //当code不在中文范围内返回false
             }

         }

          
return false;
}

posted @ 2008-05-04 21:32 Andrew Yin 阅读(116) | 评论 (0)编辑 编辑

Two weeks ago I blogged about a new MVC (Model View Controller) framework for ASP.NET that we are going to be supporting as an optional feature soon.  It provides a structured model that enforces a clear separation of concerns within applications, and makes it easier to unit test your code and support a TDD workflow.  It also helps provide more control over the URLs you publish in your applications, and can optionally provide more control over the HTML that is emitted from them.

Since then I've been answering a lot of questions from people eager to learn more about it.  Given the level of interest I thought it might make sense to put together a few blog posts that describe how to use it in more detail.  This first post is one of several I'll be doing in the weeks ahead.

A Simple E-Commerce Storefront Application

I'm going to use a simple e-commerce store application to help illustrate how the ASP.NET MVC Framework works.  For today's post I'll be implementing a product listing/browsing scenario in it.

Specifically, we are going to build a store-front that enables end-users to browse a list of product categories when they visit the /Products/Categories URL on the site:

When a user clicks on a product category hyperlink on the above page, they'll navigate to a product category listing URL - /Products/List/CategoryName -  that lists the active products within the specific category:

When a user clicks an individual product, they'll navigate to a product details URL - /Products/Detail/ProductID - that displays more details about the specific product they selected:

We'll build all of the above functionality using the new ASP.NET MVC Framework.  This will enable us to maintain a "clean separation of concerns" amongst the different components of the application, and enable us to more easily integrate unit testing and test driven development.

Creating A New ASP.NET MVC Application

The ASP.NET MVC Framework includes Visual Studio Project Templates that make it easy to create a new web application with it.  Simply select the File->New Project menu item and choose the "ASP.NET MVC Web Application" template to create a new web application using it.

By default when you create a new application using this option, Visual Studio will create a new solution for you and add two projects into it.  The first project is a web project where you'll implement your application.  The second is a testing project that you can use to write unit tests against it:

You can use any unit testing framework (including NUnit, MBUnit, MSTest, XUnit, and others) with the ASP.NET MVC Framework.  VS 2008 Professional now includes built-in testing project support for MSTest (previously in VS 2005 this required a Visual Studio Team System SKU), and our default ASP.NET MVC project template automatically creates one of these projects when you use VS 2008. 

We'll also be shipping project template downloads for NUnit, MBUnit and other unit test frameworks as well, so if you prefer to use those instead you'll also have an easy one click way to create your application and have a test project immediately ready to use with it.

Understanding the Folder Structure of a Project

The default directory structure of an ASP.NET MVC Application has 3 top-level directories:

  • /Controllers
  • /Models
  • /Views

As you can probably guess, we recommend putting your Controller classes underneath the /Controllers directory, your data model classes underneath your /Models directory, and your view templates underneath your /Views directory. 

While the ASP.NET MVC framework doesn't force you to always use this structure, the default project templates use this pattern and we recommend it as an easy way to structure your application.  Unless you have a good reason to use an alternative file layout, I'd recommend using this default pattern.

Mapping URLs to Controller Classes

In most web frameworks (ASP, PHP, JSP, ASP.NET WebForms, etc), incoming URLs typically map to template files stored on disk.  For example, a "/Products.aspx" or "/Products.php" URL typically has an underlying Products.aspx or Products.php template file on disk that handles processing it.  When a http request for a web application comes into the web server, the web framework runs code specified by the template file on disk, and this code then owns handling the processing of the request.  Often this code uses the HTML markup within the Products.aspx or Products.php file to help with generating the response sent back to the client.

MVC frameworks typically map URLs to server code in a different way.  Instead of mapping URLs to template files on disk, they instead map URLs directly to classes.  These classes are called "Controllers" and they own processing incoming requests, handling user input and interactions, and executing appropriate application and data logic based on them.  A Controller class will then typically call a separate "View" component that owns generating the actual HTML output for the request.

The ASP.NET MVC Framework includes a very powerful URL mapping engine that provides a lot of flexibility in how you map URLs to Controller classes.  You can use it to easily setup routing rules that ASP.NET will then use to evaluate incoming URLs and pick a Controller to execute.  You can also then have the routing engine automatically parse out variables that you define within the URL and have ASP.NET automatically pass these to your Controller as parameter arguments.  I'll be covering more advanced scenarios involving the URL routing engine in a future blog post in this series.

Default ASP.NET MVC URL Routing to Controller Classes

By default ASP.NET MVC projects have a preconfigured set of URL routing rules that enable you to easily get started on an application without needing to explicitly configure anything.  Instead you can start coding using a default set of name-based URL mapping conventions that are declared within the ASP.NET Application class of the Global.asax file created by the new ASP.NET MVC project template in Visual Studio. 

The default naming convention is to map the leading URL path of an incoming HTTP request (for example: /Products/) to a class whose name follows the pattern UrlPathController (for example: by default a URL leading with /Products/ would map to a class named ProductsController).

To build our e-commerce product browsing functionality, we'll add a new "ProductsController" class to our project (you can use the "Add New Item" menu in Visual Studio to easily create a Controller class from a template):

Our ProductsController class will derive from the System.Web.MVC.Controller base class.  Deriving from this base class isn't required - but it contains some useful helper methods and functionality that we'll want to take advantage of later:

Once we define this ProductsController class within our project, the ASP.NET MVC framework will by default use it to process all incoming application URLs that start under the "/Products/" URL namespace.  This means it will be automatically called to process the "/Products/Categories", "/Products/List/Beverages", and "/Products/Detail/3" URLs that we are going to want to enable within our store-front application.

In a future blog post we'll also add a ShoppingCartController (to enable end users to manage their shopping carts) and an AccountController (to enable end users to create new membership accounts on the site and login/logout of it).  Once we add these two new controller classes to our project, URLs that start with /ShoppingCart/ and /Account/ will automatically be routed to them for processing.

Note: The ASP.NET MVC framework does not require that you always use this naming convention pattern.  The only reason our application uses this by default is because there is a mapping rule that configures this that was automatically added to our ASP.NET Application Class when we created the new ASP.NET MVC Project using Visual Studio.  If you don't like this rule, or want to customize it to use a different URL mapping pattern, just go into the ASP.NET Application Class (in Global.asax) and change it. I'll cover how to-do this in a future blog post (when I'll also show some of the cool scenarios the URL routing engine enables).

Understanding Controller Action Methods

Now that we have a created a ProductsController class in our project we can start adding logic to handle the processing of incoming "/Products/" URLs to the application.

When defining our e-commerce storefront use cases at the beginning of this blog post, I said we were going to implement three scenarios on the site: 1) Browsing all of the Product Categories, 2) Listing Products within a specific Category, and 3) Showing Details about a Specific Product.  We are going to use the following SEO-friendly URLs to handle each of these scenarios:

URL Format Behavior URL Example
/Products/Categories Browse all Product Categories /Products/Categories
/Products/List/Category List Products within a Category /Products/List/Beverages
/Products/Detail/ProductID Show Details about a Specific Product /Products/Detail/34

There are a couple of ways we could write code within our ProductsController class to process these three types of incoming URLs.  One way would be to override the "Execute" method on the Controller base class and write our own manual if/else/switching logic to look at the incoming URL being requested and then execute the appropriate logic to process it.

A much easier approach, though, is to use a built-in feature of the MVC framework that enables us to define "action methods" on our controller, and then have the Controller base class automatically invoke the appropriate action method to execute based on the URL routing rules in use for our application.

For example, we could add the below three controller action methods to our ProductsController class to handle our three e-commerce URL scenarios above:

The URL routing rules that are configured by default when a new project is created treat the URL sub-path that follows the controller name as the action name of the request.  So if we receive a URL request of /Products/Categories, the routing rule will treat "Categories" as the name of the action, and the Categories() method will be invoked to process the request.  If we receive a URL request of /Products/Detail/5, the routing rule will treat "Detail" as the name of the action, and the Detail() method will be invoked to process the request, etc. 

Note: The ASP.NET MVC framework does not require that you always use this action naming convention pattern.   If you want to use a different URL mapping pattern, just go into the ASP.NET Application Class (in Global.asax) and change it.

Mapping URL Parameters to Controller Action Methods

There are several ways to access URL parameter values within the action methods of Controller classes.

The Controller base class exposes a set of Request and Response objects that can be used.  These objects have the exact same API structure as the HttpRequest/HttpResponse objects that you are already familiar with in ASP.NET.  The one important difference is that these objects are now interface based instead of sealed classes (specifically: the MVC framework ships with new System.Web.IHttpRequest and System.Web.IHttpResponse interfaces).  The benefit of having these be interfaces is that it is now easy to mock them - which enables easy unit testing of controller classes.  I'll cover this in more depth in a future blog post. 

Below is an example of how we could use the Request API to manually retrieve an ID querystring value from within our Detail action method in the ProductsController class:

The ASP.NET MVC framework also supports automatically mapping incoming URL parameter values as parameter arguments to action methods.  By default, if you have a parameter argument on your action method, the MVC framework will look at the incoming request data to see if there is a corresponding HTTP request value with the same name.  If there is, it will automatically pass it in as a parameter to your action method.

For example, we could re-write our Detail action method to take advantage of this support and make it cleaner like below:

In addition to mapping argument values from the querystring/form collection of a request, the ASP.NET MVC framework also allows you to use the MVC URL route mapping infrastructure to embed parameter values within the core URL itself (for example: instead of /Products/Detail?id=3 you could instead use /Products/Detail/3). 

The default route mapping rule declared when you create a new MVC project is one with the format: "/[controller]/[action]/[id]".  What this means is that if there is any URL sub-path after the controller and action names in the URL, it will by default be treated as a parameter named "id" - and which can be automatically passed into our controller action method as a method argument.

This means that we can now use our Detail method to also handle taking the ID argument from the URL path (e.g: /Products/Detail/3):

I can use a similar approach for the List action so that we can pass in the category name as part of the URL (for example: /Products/List/Beverages).  In the interest of making the code more readable, I made one tweak to the routing rules so that instead of having the argument name be called "id" it will be called "category" for this action.

Below is a version of our ProductsController class that now has full URL routing and parameter mapping support implemented:

Note above how the List action takes the category parameter as part of the URL, and then an optional page index parameter as a querystring (we'll be implementing server-side paging and using that value to indicate which page of category data to display with the request). 

Optional parameters in our MVC framework are handled using nullable type arguments on Controller Action methods.  Because the page parameter on our List action is a nullable int (that is what "int?" means syntactically), the MVC framework will either pass in a value if it is present in the URL - or pass in null if not.  Check out my previous post on the ?? null coalescing operator to learn a useful tip/trick on how to work with nullable types that are passed as arguments like this.

Building our Data Model Objects

We now have a ProductsController class and three action methods on it ready to process incoming web requests.  Our next step will be to build some classes that will help us work with our database to retrieve the appropriate data needed to handle these web requests.

In an MVC world "models" are the components of an application that are responsible for maintaining state.  With web applications this state is typically persisted inside a database (for example: we might have a Product object that is used to represent product data from the Products table inside our SQL database).

The ASP.NET MVC Framework enables you to use any data access pattern or framework you want in order to retrieve and manage your models.  If you want to use ADO.NET DataSets/DataReaders (or abstractions built on top of them) you can.  If you prefer to use an object relational mapper (ORM) like NHibernate, LLBLGen, WilsonORMapper, LINQ to SQL/LINQ to Entities you can absolutely use those as well.

For our e-commerce sample application I'm going to use the built-in LINQ to SQL ORM shipped in .NET 3.5 and VS 2008.  You can learn more about LINQ to SQL from my ongoing blog tutorial series that covers it (in particular make sure to check out my Part1, Part2, Part3 and Part4 posts). 

I'll start by right-clicking on the "Models" sub-directory of our MVC web project inside VS and choose the "Add New Item" option to add a LINQ to SQL model.  Within the LINQ to SQL ORM designer I'll define three data model classes that map to the Categories, Products, and Suppliers table inside the SQL Server Northwind sample database (read Part 2 of my LINQ to SQL series to learn how to-do this):

Once we've defined our LINQ to SQL data model classes, I'll then add a new NorthwindDataContext partial class to our Models directory as well:

Within this class I'll define a few helper methods that encapsulate some LINQ expressions that we can use to retrieve the unique Category objects from our database, retrieve all Product objects within a specific category in our database, as well as retrieve an individual Product object based on a supplied ProductID:

These helper methods will make it easy for us to cleanly retrieve the data model objects needed from our ProductsController class (without having to write the LINQ expressions within the Controller class itself):

We now have all of the data code/objects we need to finish implementing our ProductsController functionality. 

Finishing the Implementation of our ProductsController Class

Controllers in a MVC based application are responsible for processing incoming requests, handling user input and interactions, and executing appropriate application logic based on them (retrieving and updating model data stored in a database, etc).

Controllers typically do not generate the specific HTML response for a request.  The task of generating an HTML response is instead owned by "View" components within the application - which are implemented as separate classes/templates from Controllers.  Views are intended to be focused entirely on encapsulating presentation logic, and should not contain any application logic or database retrieval code (instead all app logic should be handled by the Controller).

In a typical MVC web workflow, Controller action methods will handle the incoming web request, use the incoming parameter values to execute appropriate application logic code, retrieve or update data model objects from a database, and then select a "View" to use to render an appropriate UI response back to a browser.  As part of picking the appropriate View to render, the Controller will explicitly pass in (as arguments) all of the data and variables required by the "View" in order to for it to render the appropriate response:

You might be wondering - what is the benefit of separating the Controller and the View like this?  Why not just put them in the same class?  The primary motivation in partitioning the application like this is to help enforce the separation of your application/data logic from your UI generation code.  This makes it much easier to unit test your application/data logic in isolation from your UI rendering logic.  It can also help make your application more maintainable over time - since it makes it harder for you to accidentally add application/data logic in your view templates.

When implementing the three controller action methods of our ProductsController class, we'll use the incoming URL parameter values to retrieve the appropriate model objects from our database, and then pick a "View" component to use to render an appropriate HTML response.  We'll use one of the RenderView() methods on the Controller base class to specify the View we want to use, as well as explicitly pass in the specific data that we want the View to use to render its response.

Below is the final result of our ProductsController implementation:

Notice that the number of lines of code in our action methods above is pretty small (two lines each).  This is partly because the URL parameter parsing logic is handled entirely for us by the MVC framework (saving us from having to write a lot of this code).  This is also partly because the product browsing scenario is fairly simple from a business logic perspective (the action methods are all read-only display scenarios). 

In general, though, you'll often find that you'll have what are sometimes called "skinny controllers" - meaning controller methods full of relatively terse action methods (less than 10 lines of code).  This is often a good sign that you have cleanly encapsulated your data logic and factored your controller logic well.

Unit Testing our ProductsController

You might be surprised that the next step we are going to work on is to test our application logic and functionality.  You might ask - how is that even possible?  We haven't implemented our Views, and our application currently doesn't render a single tag of HTML.  Well, part of what makes an MVC approach attractive is that we can unit test the Controller and Model logic of applications completely independently of the View/Html generation logic.  As you'll see below we can even unit test these before we create our Views.

To unit test the ProductsController class that we've been working on, we'll add a ProductsControllerTest class into the Test Project that was added to our solution by default when we created our ASP.NET MVC Application using Visual Studio:

We'll then define a simple unit test that tests the Detail action of our ProductsController:

The ASP.NET MVC framework has been designed specifically to enable easy unit testing.  All core APIs and contracts within the framework are interfaces, and extensibility points are provided to enable easy injection and customization of objects (including the ability to use IOC containers like Windsor, StructureMap, Spring.NET, and ObjectBuilder).  Developers will be able to use built-in mock classes, or use any .NET type-mocking framework to simulate their own test versions of MVC related objects.

In the unit test above, you can see an example of how we are injecting a dummy "ViewFactory" implementation on our ProductsController class before calling the Detail() action method.  By doing this we are overriding the default ViewFactory that would otherwise handle creating and rendering our View.  We can use this test ViewFactory implementation to isolate the testing of just our ProductController's Detail action behavior (and not have to invoke the actual View to-do this).  Notice how we can then use the three Assert statements after the Detail() action method is called to verify that the correct behavior occurred within it (specifically that the action retrieved the correct Product object and then passed it to the appropriate View).

Because we can mock and simulate any object in the MVC framework (including IHttpRequest and IHttpResponse objects), you do not have to run unit tests in the context of an actual web-server.  Instead, we can create our ProductsController within a normal class library and test it directly.  This can significantly speed up the execution of unit tests, as well as simplify the configuration and running of them.

If we use the Visual Studio 2008 IDE, we can also easily track the success/failure of our test runs (this functionality is now built-into VS 2008 Professional):

I think you'll find that the ASP.NET MVC Framework makes writing tests easy, and enables a nice TDD workflow.

Rendering UI with Views

We've finished implementing and testing the application + data logic for the product browsing section of our e-commerce application.  Now we need to implement the HTML UI for it. 

We'll do this by implementing "Views" that render the appropriate UI using the view-related data objects that our ProductsController action method provided when calling the RenderView() method:

In the code example above the RenderView method's "Categories" parameter is indicating the name of the view we want to render, and the second parameter is a list of category objects that we want to pass to the view and have it use as data to generate the appropriate HTML UI for.

The ASP.NET MVC Framework supports the ability to use any templating engine to help with generating UI (including existing templating engines like NVelocity, Brail - as well as new ones you write yourself).  By default the ASP.NET MVC Framework uses the existing ASP.NET Page (.aspx), Master Page (.master), and UserControl (.ascx) support already within ASP.NET. 

We'll be using the built-in ASP.NET view engine to implement our E-commerce Application UI.

Defining a Site.Master File

Because we are going to be building many pages for our site, we'll start our UI work by first defining a master page that we can use to encapsulate the common HTML layout/chrome across the site. We'll do this in a file called "Site.Master" that we'll create under the \Views\Shared directory of our project:

We can reference an external CSS stylesheet to encapsulate all styles for the site, and then use the master page to define the overall layout of the site, as well as identify content placeholder regions where we'll want pages to be able to fill in page specific content.  We can optionally use all of the cool new VS 2008 designer features when doing this (including the HTML split-view designer, CSS Authoring, and Nested Master Pages support).

Understanding the /Views Directory Structure

By default when you create a new ASP.NET MVC Project using Visual Studio, it will create a "Shared" sub-directory underneath the "Views" directory root.  This is the recommended place to store Master Pages, User Controls, and Views that we want to share across multiple Controllers within the application.

When building views that are specific to an individual controller, the default ASP.NET MVC convention is to store them in sub-directories under the \Views root.  By default the name of a sub-directory should correspond to the Controller name.  For example, because the Controller class we have been building is called "ProductsController", we will by default store the Views specific to it within the \Views\Products sub-directory:

When we call the RenderView(string viewName) method within a specific Controller, the MVC framework will automatically first look for a corresponding .aspx or .ascx view template underneath the \Views\ControllerName directory, and then if it can't find an appropriate view template there it will check the \Views\Shared directory for one:

Creating a Categories View

We can create the "Categories" View for our ProductsController within Visual Studio by using the "Add New Item" menu option on the Products directory and selecting the "MVC View Page" item template.  This will create a new .aspx page that we can optionally associate with our Site.Master master page to pick up the overall look and feel of the site (and just like with master pages you get full WYSIWYG designer support):

When building applications using an MVC pattern, you want to keep your View code as simple as possible, and make sure that the View code is purely about rendering UI.  Application and data retrieval logic should only be written inside Controller classes.  Controller classes can then choose to pass on the necessary data objects needed to render a view when they call their RenderView method.  For example, below in the Categories action method of our ProductsController class we are passing a List collection of Category objects to the Categories view:

MVC View Pages by default derive from the System.Web.Mvc.ViewPage base class, which provides a number of MVC specific helper methods and properties that we can use in constructing our UI.  One of these ViewPage properties is named "ViewData", and it provides access to the view-specific data objects that the Controller passed as arguments to the RenderView() method.

From within your View you can access the "ViewData" in either a late-bound or strongly-typed way.  If your View derives from ViewPage, the ViewData property will be typed as a late-bound dictionary.  If your View derives from the generics based ViewPage<T> - where T indicates the data object type of the ViewData the Controller is passing to the View - then the ViewData property will be strongly typed to match the same type that your Controller passed in.

For example, my Categories View code-behind class below is deriving from ViewPage<T> - where I am indicating that T is a List of Category objects:

This means that I get full type-safety, intellisense, and compile-time checking within my View code when working against the ProductsController.Categories() supplied List<Category> ViewData:

Rendering our Categories View:

If you remember from the screenshots at the very beginning of this post, we want to display a list of the product categories within our Categories view:

I can write this HTML UI generation code in one of two ways within my Categories View implementation: 1) Using Inline Code within the .aspx file, or 2) Using Server Controls within the .aspx file and Databinding from my Code Behind

Rendering Approach 1: Using Inline Code

ASP.NET Pages, User Controls and Master Pages today support <% %> and <%= %> syntax to embed rendering code within html markup.  We could use this technique within our Categories View to easily write a foreach loop that generates a bulleted HTML category list:

VS 2008 provides full code intellisense within the source editor for both VB and C#.  This means we'll get intellisense against our Category model objects passed to the View:

VS 2008 also provides full debugger support for inline code as well (allowing us to set breakpoints and dynamically inspect anything in the View with the debugger):

Rendering Approach 2: Using Server Side Controls

 

ASP.NET Pages, User Controls and Master Pages also support the ability to use declarative server controls to encapsulate HTML UI generation.  Instead of using inline code like above, we could use the new <asp:listview> control in .NET 3.5 to generate our bulleted list UI instead:

Notice above how the ListView control encapsulates both rendering a list of values, as well as handles the scenario where no items are in the list (the <EmptyDataTemplate> saves us from having to write an if/else statement in the markup).  We could then databind our list of category objects to the listview control using code-behind code like below:

Important: In a MVC world we only want to put rendering logic in our View's code-behind class (and not any application or data logic).  Notice above how the only logic we have is to assign the strongly typed ViewData collection of Category objects to the ListView control.   Our ProductsController Controller class is the one that actually retrieves the list of Categories from the database - not the View. 

This ListView server-control version of our View template will then generate the exact same HTML as our in-line code version.  Because we don't have a <form runat="server"> control on the page, no viewstate, ID values or other markup will be emitted.  Only pure CSS friendly HTML:

 

Html.ActionLink Method

One of the things you might have noticed in both the inline-code and the server-control versions of the View code snippets above are calls to an Html.ActionLink method:

The Html object is a helper property on the ViewPage base class, and the ActionLink method is a helper on it that makes it easy to dynamically generate HTML hyperlinks that link back to action methods on Controllers. If you look at the HTML output picture in the section above, you can see some example HTML output generated by this method:

<a href="http://weblogs.asp.net/Products/List/Beverages">Beverages</a>

The signature of the Html.ActionLink helper method I am using looks like this:

string ActionLink(string text, object values);

The first argument represents the inner content of the hyperlink to render (for example: <a>text goes here</a>).  The second argument is an anonymous object that represents a sequence of values to use to help generate the actual URL (you can think of this as a cleaner way to generate dictionaries).  I will go into more detail on how exactly this works in a future blog post that covers the URL routing engine.  The short summary, though, is that you can use the URL routing system both to process incoming URLs, as well as to generate URLs that you can emit in outgoing HTML.  If we have a routing rule like this:

/<controller>/<action>/<category>

And then write this code within a ProductController's Category View:

<%= Html.ActionLink("Click Me to See Beverages", new { action="List", category="Beverages" } %>

The ActionLink method will use the URL mapping rules of your application to swap in your parameters and generate this output:

<a href="http://weblogs.asp.net/Products/List/Beverages">Click Me to See Beverages</a>

This makes it easy within your application to generate URLs and AJAX callbacks to your Controllers.  It also means you can update your URL routing rules in one place and have the code throughout your application automatically pick up the changes for both incoming URL processing and outgoing URL generation.

Important Note: To help enforce testability, the MVC framework today does not support postback events directly to server controls within your Views.  Instead, ASP.NET MVC applications generate hyperlink and AJAX callbacks to Controller actions - and then use Views (and any server controls within them) solely to render output.  This helps ensure that your View logic stays minimal and solely focused on rendering, and that you can easily unit test your Controller classes and verify all Application and Data Logic behavior independent of your Views.  I'll blog more about this in the future.

Summary

This first blog post is a pretty long one, but hopefully helps provide a reasonably broad look at how all the different components of the new ASP.NET MVC Framework fit together, and how you can build a common real world scenario with it.  The first public preview of the ASP.NET MVC bits will be available in a few weeks, and you'll be able to use them to do all of the steps I outlined above.

While many of the concepts inherent to MVC (in particular the idea of separation of concerns) are probably new to a lot of people reading this, hopefully this blog post has also show how the ASP.NET MVC implementation we've been working on fits pretty cleanly into the existing ASP.NET, .NET, and Visual Studio feature-set.  You can use .ASPX, .ASCX and .MASTER files and ASP.NET AJAX to create your ASP.NET MVC Views.  Non-UI features in ASP.NET today like Forms Authentication, Windows Authentication, Membership, Roles, Url Authorization, Caching, Session State, Profiles, Health Monitoring, Configuration, Compilation, Localization, and HttpModules/HttpHandlers all fully support the MVC model.

If you don't like the MVC model or don't find it natural to your style of development, you definitely don't have to use it.  It is a totally optional offering - and does not replace the existing WebForms Page Controller model.  Both WebForms and MVC will be fully supported and enhanced going forward.  You can even build a single application and have parts of it written using WebForms and parts written using an MVC approach if you want.

If you do like what you've seen from the above MVC post (or are intrigued and want to learn more), keep an eye on my blog over the weeks ahead.  I'll be covering more MVC concepts and use them to build out our e-commerce application to show more features of it.

Hope this helps,

Scott

posted @ 2008-05-04 19:44 Andrew Yin 阅读(15) | 评论 (0)编辑 编辑

One of the things that many people have asked for over the years with ASP.NET is built-in support for developing web applications using a model-view-controller (MVC) based architecture.

Last weekend at the Alt.NET conference in Austin I gave the first public demonstration of a new ASP.NET MVC framework that my team has been working on.  You can watch a video of my presentation about it on Scott Hanselman's blog here.

We'll be releasing a public preview of this ASP.NET MVC Framework a little later this year.  We'll then ship it as a fully supported ASP.NET feature in the first half of next year.

What is a Model View Controller (MVC) Framework?

MVC is a framework methodology that divides an application's implementation into three component roles: models, views, and controllers.

  • "Models" in a MVC based application are the components of the application that are responsible for maintaining state.  Often this state is persisted inside a database (for example: we might have a Product class that is used to represent order data from the Products table inside SQL).
  • "Views" in a MVC based application are the components responsible for displaying the application's user interface.  Typically this UI is created off of the model data (for example: we might create an Product "Edit" view that surfaces textboxes, dropdowns and checkboxes based on the current state of a Product object).
  • "Controllers" in a MVC based application are the components responsible for handling end user interaction, manipulating the model, and ultimately choosing a view to render to display UI.  In a MVC application the view is only about displaying information - it is the controller that handles and responds to user input and interaction.

One of the benefits of using a MVC methodology is that it helps enforce a clean separation of concerns between the models, views and controllers within an application.  Maintaining a clean separation of concerns makes the testing of applications much easier, since the contract between different application components are more clearly defined and articulated.

The MVC pattern can also help enable red/green test driven development (TDD) - where you implement automated unit tests, which define and verify the requirements of new code, first before you actually write the code itself.

A few quick details about the ASP.NET MVC Framework

I'll be doing some in-depth tutorial posts about the new ASP.NET MVC framework in a few weeks once the bits are available for download (in the meantime the best way to learn more is to watch the video of my Alt.net presentation).

A few quick details to share in the meantime about the ASP.NET MVC framework:

  • It enables clean separation of concerns, testability, and TDD by default.  All core contracts within the MVC framework are interface based and easily mockable (it includes interface based IHttpRequest/IHttpResponse intrinsics).  You can unit test the application without having to run the Controllers within an ASP.NET process (making unit testing fast).  You can use any unit testing framework you want to-do this testing (including NUnit, MBUnit, MS Test, etc).
  • It is highly extensible and pluggable.  Everything in the MVC framework is designed so that it can be easily replaced/customized (for example: you can optionally plug-in your own view engine, routing policy, parameter serialization, etc).  It also supports using existing dependency injection and IOC container models (Windsor, Spring.Net, NHibernate, etc).
  • It includes a very powerful URL mapping component that enables you to build applications with clean URLs.  URLs do not need to have extensions within them, and are designed to easily support SEO and REST-friendly naming patterns.  For example, I could easily map the /products/edit/4 URL to the "Edit" action of the ProductsController class in my project above, or map the /Blogs/scottgu/10-10-2007/SomeTopic/ URL to a "DisplayPost" action of a BlogEngineController class.
  • The MVC framework supports using the existing ASP.NET .ASPX, .ASCX, and .Master markup files as "view templates" (meaning you can easily use existing ASP.NET features like nested master pages, <%= %> snippets, declarative server controls, templates, data-binding, localization, etc).  It does not, however, use the existing post-back model for interactions back to the server.  Instead, you'll route all end-user interactions to a Controller class instead - which helps ensure clean separation of concerns and testability (it also means no viewstate or page lifecycle with MVC based views).
  • The ASP.NET MVC framework fully supports existing ASP.NET features like forms/windows authentication, URL authorization, membership/roles, output and data caching, session/profile state management, health monitoring, configuration system, the provider architecture, etc.

Summary

If you are looking to build your web applications using a MVC approach, I think you'll find this new ASP.NET MVC Framework option very clean and easy to use.  It will enable you to easily maintain separation of concerns in your applications, as well as facilitate clean testing and TDD. 

I'll post more tutorials in the weeks ahead on how the new MVC features work, as well as how you can take advantage of them.

Hope this helps,

Scott

posted @ 2008-05-04 19:41 Andrew Yin 阅读(34) | 评论 (0)编辑 编辑

const
用于修改字段或局部变量的声明。它指定字段或局部变量的值是常数,不能被修改。常量的值必须在编译的时候确定,编译后,CLR将常量的值保存在Assembly的怨数据中。如果变量是const,那么他隐式的是static的。因此在声明常数的时候只需将该变量声明为const即可,而不允许在声明常数的时候使用static。
当代码引用常量时,CLR在元数据中查找该符号,将提取的常量值嵌入到IL中,所以常量没有地址以及相应的分配内存,而且不能通过引用传递变量

readonly
在字段上使用的修饰符,表示该字段是只读的。当一个字段在声明为readonly的时候,只有两种方式可以对其赋值,即作为声明的一部分出现,或者在同一类的构造函数中。
对于实例字段,在包含字段声明的类的实例构造函数中;或者,对于静态字段,在包含字段声明的类的静态构造函数中。也只有在这些上下文中,将 readonly 字段作为 out 或 ref 参数传递才有效。

static
声明属于类型本身而不是属于特定对象的静态成员,可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。
尽管类的实例包含该类所有实例字段的单独副本,但每个静态字段只有一个副本
不能通过类的实例引用静态成员,只可以通过类型名称引用它
如果对类应用 static 关键字,则该类的所有成员都必须是静态的
类(包括静态类)可以有静态构造函数。在程序开始和实例化类之间的某个时刻调用静态构造函数

const和readonly
const
1. 在编译期间解析的常量
2. 必须在声明就初始化
3. 既可用来修饰类中的成员,也可修饰函数体内的局部变量。
readonly
1. 在运行期间解析的常量,
2. 既可以在声明时初始化也可以在构造器中初始化,因此根据使用的构造函数,readonly的字段可能具有不同的值。
3. 只可以用于修饰类中的成员

const和static readonly
都表示静态的常量,赋值以后都不可以更改


我们都知道,const和static readonly的确很像:通过类名而不是对象名进行访问,在程序中只读等等。在多数情况下可以混用。
二者本质的区别在于,const的值是在编译期间确定的,因此只能在声明时通过常量表达式指定其值。而static readonly是在运行时计算出其值的,所以还可以通过静态构造函数来赋值。
明白了这个本质区别,我们就不难看出下面的语句中static readonly和const能否互换了:
1. static readonly MyClass myins = new MyClass();
2. static readonly MyClass myins = null;
3. static readonly A = B * 20;
   static readonly B = 10;
4. static readonly int [] constIntArray = new int[] {1, 2, 3};
5. void SomeFunction()
    {
      const int a = 10;
       ...
    }

1:不可以换成const。new操作符是需要执行构造函数的,所以无法在编译期间确定
2:可以换成const。我们也看到,Reference类型的常量(除了String)只能是Null。
3:可以换成const。我们可以在编译期间很明确的说,A等于200。
4:不可以换成const。道理和1是一样的,虽然看起来1,2,3的数组的确就是一个常量。
5:不可以换成readonly,readonly只能用来修饰类的field,不能修饰局部变量,也不能修饰property等其他类成员。

因此,对于那些本质上应该是常量,但是却无法使用const来声明的地方,可以使用static readonly。例如C#规范中给出的例子:

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);
static readonly需要注意的一个问题是,对于一个static readonly的Reference类型,只是被限定不能进行赋值(写)操作而已。而对其成员的读写仍然是不受限制的。

public static readonly MyClass myins = new MyClass();

myins.SomeProperty = 10;  //正常
myins = new MyClass();    //出错,该对象是只读的

但是,如果上例中的MyClass不是一个class而是一个struct,那么后面的两个语句就都会出错。

 

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
     {
         red = r;
         green = g;
         blue = b;
     }
}



C#拥有两种不同的常量:静态常量(compile-time constants)和动态常量(runtime constants)。它们有不同的特性,错误的使用不仅会损失效率,还可能造成错误。相比之下,静态常量在速度上会稍稍快一些,但是灵活性却比动态常量差很多。

//静态常量(隐式是静态的)
public const int compiletimeConstant = 1;
//动态常量
public static readonly runtimeConstant = 1;

      静态常量在编译时会将其替换为所对应的值,也就是说下面这2句话通过编译器编译后产生的IL是一样的。

//通过编译后二者会被翻译成相同的中间语言
int myNum = compiletimeConstant;
int myNum = 1;

      动态常量的值是在运行时获得的。IL中将其标为只读常量,而不是用常量的值代替。

      静态常量只能被声明为简单的数据类型(内建的int和浮点型)、枚举或字符串。下面的程序段是通不过编译的。你不能用new关键字初始化一个静态常量,即便是对一个值类型来说。

//这样是错误的
public const DateTime myDateTime = new DateTime(2006,9,1,0,0,0);
//这样是可以的
public static readonly DateTime myDateTime = new DateTime(2006,9,1,0,0,0);

       只读数据也是常量的一种,它们不能在构造器初始化之后被修改。但是它同静态常量不同,它的值是在运行时才被指派的,因此就会获得更大的灵活性。动态常量可以是任意的数据类型。

       二者最大的差别在于:静态常量在编译时会将其换为对应的值,这就意味着对于不同的程序集来说,当你改变静态常量的时候需要将其重新编译,否则常量的值不会发生变化,可能引发潜在的问题,而动态常量就不会有这种情况。

     用const定义的常量(隐式是静态的),需要像访问静态成员那样去访问const定义的常量,而用对象的成员方式去访问会出编译错误。 声明的同时要设置常量值。
      从另一方面来说,如果你的确要声明一些从不改变且处处唯一的常量,例如钩子函数SetWindowsHookEx的idHook参数或序列化时的版本等,就应该使用静态常量。但是用到这样的常量的机会不多。一般来说我们应该使用灵活性更高的动态常量。

               静态常量            动态常量

内存消耗        无                   因为要保存常量 有消耗

初始化          很少的简单类型,     任意类型,可以在类构造函数中赋值
                不能new,必须在
                声明同时赋值

何时发挥作用   编译时进行替换       相当于类中的数据成员

posted @ 2008-05-04 14:49 Andrew Yin 阅读(298) | 评论 (1)编辑 编辑

从我开始参加工作开始,就有不少朋友托我找工作。随着工作时间越来越长,认识的牛人也越来越多,竟然也有朋友的公司缺人找我推荐人才的。但我发现托我找工作的人里,竟然没有一个能符合找我推荐人才的人的需求,至今我竟然没有促成一对“良缘”。

每当有朋友找我推荐工作,我总是事先就告知他们,我手头只有.NET开发方面的职位机会,当然有些朋友就“知难而退”了(没办法,人家或是java高人,或是对市场感兴趣)。但更多的朋友则是不熟悉.NET的,却希望能得到一个机会,并不断承诺自己“学习能力强”,“只要给个机会,不出一个月肯定能胜任”。

Anders Liu当然是相信自己的朋友的,所以也推荐过一些。但马上就发现,他们并不能得到公司的认可,甚至连个“学习机会”都得不到。这让朋友们很痛苦,Anders Liu也很痛苦,等待天降人才的朋友公司,想必也会郁闷……

这究竟是为什么呢?

个人认为,可能是“学习能力”在这几年被过分地夸大了。

公司招人为了什么?赚钱。再简单不过的答案。员工的学习能力的确是公司很重要的财富,毕竟没有人愿意雇用什么都需要别人教的人。但是,员工的生产力才是一个公司得以生存和发展的真正动力。

那么,究竟什么样的人才才是企业最需要的呢?1-已经具备很强的业务能力,能快速投入生产并能持续稳定地产生价值;2-学习能力强,能提高自身产量。前者是必要条件,后者仅是锦上添花而已。

因此,学习能力强实际上只是整个简历中闪亮的一笔而已,并不代表全部。

所以,切忌,在找工作写简历时,如果不是已经有足够的能力,还是不要太多强调自己的学习能力。没有实际知识基础的学习能力是很空洞的。

如果你能承诺“如果给我一个月的时间,我就能学会”,那么,为什么不先放下一切去学上一个月再找工作呢?总比下个月还在重复这句话要来得实在吧?

有朋友也许要反对了,在公司有资源、有大环境,我一个人在家怎么学?呵呵,并不是每个公司都有资源、有大环境的,很多公司甚至希望你去帮助公司搜集有用的资源,帮助公司建立大环境呢~

所以,如果你真的要干这行,先用一个月时间,踏踏实实学习一下吧,这会给你节省好几个月的损失呢。

这只是Anders Liu个人对学习能力和找工作的一些看法,希望更多的朋友能得到自己需要的,能真正实现自己的价值。

(如果想骂Anders Liu,先看看自己的书架上都有啥书,有哪几本是真正用心看过的。三思而后骂:P)

posted @ 2008-05-04 14:12 Andrew Yin 阅读(35) | 评论 (1)编辑 编辑

原文:http://www.codeproject.com/csharp/SingleInstanceApplication.asp
翻译:Anders Liu
出处:http://www.cnblogs.com/AndersLiu/archive/2007/07/09/811354.html

简介

  本文解决下列问题:
1 创建单实例应用程序。
2 当用户试图启动新的实例时,恢复前一个实例。
3 当窗口关闭时间起最小化到任务栏的通知区域中(带动画)。

如何创建单实例应用程序?

  通常,需要确保在任何时候都只有程序的一个实例在运行。如果用户试图运行另外一个实例,或者通知用户已经有一个实例了,或者激活之前运行的实例并将其带到前台。对于Windows应用程序,我们可能希望恢复现有的主窗口。因此当应用程序启动的时候,应该查看是否已经有正在运行的实例了。如果有,应该退出当前实例并激活前一个实例的主窗口并显示给用户。

  将应用程序做成单实例的,可以通过mutex(Mutual Exclusion Semaphore)[互斥体(互斥信号量)]来实现。Windows应用程序通过Application.Run()方法来加载住窗体。在Main方法中,创建一个新的mutex。如果可以创建新的mutex,则允许应用程序运行。如果mutex已经被创建了,应用程序就不会启动。这样就能确保任何使用只有一个实例在运行。

// 用于检测是否创建了新的mutex
bool newMutexCreated = false;
// mutex的名字以Local\作为前缀,
// 确保将其创建在每会话(per-session)命名空间中,
// 而不是全局命名空间中。
string mutexName = "Local\\" +
  System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

Mutex mutex = null;
try
{
    // 使用唯一的名字创建一个新的mutex
    mutex = new Mutex(false, mutexName, out newMutexCreated);
}
catch(Exception ex)
{
    MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
         "\n\n"+"Application Exiting...","Exception thrown");
    Application.Exit ();
}

// 如果是第一次创建mutex,则启动应用程序实例,
// 因为这是第一次运行
if(newMutexCreated)
{
    Application.Run(new AnimatedWindowForm());
}

  当创建mutex时,其名字的前缀可以是Globa\或Local\。Global\前缀意味着该mutex将影响全局命名空间。

  以Local\为前缀意味着该mutex只会影响到用户会话命名空间。

  Windows XP和Windows 2003允许通过Terminal Services Sessions(终端服务会话)快速切换用户。因此如果mutext使用Global\前缀,在整个系统范围内只能有一个实例运行。如果一个用户启动了该应用程序,其他用户就无法在他们的会话中再次创建一个实例了。如果mutext的前缀不是Local\,它也只会影响每个会话。

  要了解Kernel Object命名空间,请阅读这篇MSDN文章(http://msdn.microsoft.com/library/en-us/termserv/termserv/kernel_object_namespaces.asp)。

  现在,还有一个任务要完成——将前一个实例移到前台。在Windows应用程序中这意味着将应用程序主窗口恢复到顶端,如果已经隐藏,则显示给用户。

恢复前一个实例

  要恢复主窗口,必须要得到应用程序主窗口的句柄。通过下面这段代码可以得到进程的MainWindowHandle:

Process[] currentProcesses =
    Process.GetProcessesByName("SingleInstanceApplication");
System.IntPtr mainWindowHandle = currentProcesses[0].MainWindowHandle;
if(mainWindowHandle != IntPtr.Zero)
{
    ShowWindow(mainWindowHandle,SW_RESTORE); // Restore the Window
    UpdateWindow(mainWindowHandle);
}

  但当应用程序的主窗口被隐藏时,这段代码会失败,因为句柄返回的是0。

  一个可靠的机制是使MainWindowHandle变成必需的。这就轮到共享内存上场了。共享内存是IPC(Inter Process Communication,进程间通信)的一种方法,使用这种方法,两个或更多个进程可以使用共享的内存片段进行通信。在C#中创建共享内存可以使用Win32 API调用。内存映射可以将文件内容关联到你的进程地址空间或系统页文件或系统内存的特定地址中的一个特定的地址区域。

  要在两个进程之间共享数据,需要在系统页文件中创建共享内存。

  为了使一个进程能够将通过内存映射文件(Memory Mapped File,MMF)将数据共享给其他进程,每个进程都必须访问该文件。这通过为MMF对象起一个名字来实现,每个进程都能够使用这个名字来访问共享内存。

private const int INVALID_HANDLE_VALUE = -1;
private const int FILE_MAP_WRITE = 0x2;
private const int FILE_MAP_READ = 0x0004;

[DllImport("kernel32.dll",EntryPoint="OpenFileMapping",
              SetLastError=true, CharSet=CharSet.Auto) ]
private static extern IntPtr OpenFileMapping (int
    wDesiredAccess, bool bInheritHandle,String lpName );

[DllImport("Kernel32.dll",EntryPoint="CreateFileMapping",
                 SetLastError=true,CharSet=CharSet.Auto)]

private static extern IntPtr CreateFileMapping(int hFile,
        IntPtr lpAttributes, uint flProtect,
        uint dwMaximumSizeHigh, uint dwMaximumSizeLow,
        string lpName);
   
[DllImport("Kernel32.dll")]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
        uint dwDesiredAccess, uint dwFileOffsetHigh,
        uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
   
[DllImport("Kernel32.dll",EntryPoint="UnmapViewOfFile",
        SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

[DllImport("kernel32.dll",EntryPoint="CloseHandle",
        SetLastError=true,CharSet=CharSet.Auto)]
private static extern bool CloseHandle(uint hHandle);
[DllImport("kernel32.dll",EntryPoint="GetLastError",
        SetLastError=true,CharSet=CharSet.Auto)]

private static extern uint GetLastError();
private IntPtr memoryFileHandle;

public enum FileAccess : int
{
    ReadOnly = 2,
    ReadWrite = 4
}

  为共享内存对象创建新的MMF,可以使用CreateFileMapping()函数。创建了新的MMF对象后,系统页文件就会为其保留一部分。

参数

  • hFile——要进行内存映射的文件句柄。当在系统页文件中创建MMF时,这个值必须是0xFFFFFFFF(-1)。
  • lpAttributes——指向一个SECURITY_ATTRIBUTES结构体的指针
  • flProtect——为内存映射文件指定的保护类型。
    •  PAGE_READONLY——只读访问。
    •  PAGE_READWRITE——读/写访问。
    •  PAGE_WRITECOPY——Copy-on-write访问。
    •  PAGE_EXECUTE_READ——读取和执行访问。
    •  PAGE_EXECUTE_READWRITE——读取、写入和执行访问。
  • dwMaximumSizeHigh——文件映射对象的最大大小的DWORD值的高位。
  • dwMaximumSizeLow——文件映射对象的最大大小的DWORD值的低位。
  • lpName——文件映射对象的名字。

public static MemoryMappedFile CreateMMF(string fileName, FileAccess access, int size)
{
    if(size < 0)
        throw new ArgumentException("The size parameter" +
            " should be a number greater than Zero.");

    IntPtr memoryFileHandle = CreateFileMapping (0xFFFFFFFF,
        IntPtr.Zero,(uint)access,0,(uint)size,fileName);

    if(memoryFileHandle == IntPtr.Zero)
        throw new SharedMemoryException("Creating Shared Memory failed.");

    return new MemoryMappedFile(memoryFileHandle);
}

  下面我们启动应用程序的第一个实例,创建MMF对象。

// 当第一次创建mutex时,运行程序,因为这是第一个实例。
if(newMutexCreated)
{
    //Create the Shared Memory to store the window handle.
    lock(typeof(AnimatedWindowForm))
    {
        sharedMemory = MemoryMappedFile.CreateMMF("Local\\" +
            "sharedMemoryAnimatedWindow",
            MemoryMappedFile.FileAccess .ReadWrite, 8);
    }
    Application.Run(new AnimatedWindowForm());
}

  一旦得到了内存映射文件的句柄,就可以用它来将文件视图映射到调用进程的地址空间。只要MMF对象存活着,就能对视图进行映射和取消映射。MapViewOfFile()和UnmapViewOfFile()函数用于映射和取消映射视图。我们是否可以执行读/写操作,取决于在调用MapViewOfFile()函数时指定的访问类型。

  MapViewOfFile()的参数:

  • hFileMappingObject——MMF对象的句柄。CreateFileMapping和OpenFileMapping函数可以返回这个句柄。
  • dwDesiredAccess——MMF对象的访问类型。这个参数可以取下列值:
    •  FILE_MAP_READ——只读访问。MMF对象必须具备PAGE_READWRITE或PAGE_READONLY访问。
    •  FILE_MAP_WRITE——读/写访问。MMF对象必须具备PAGE_READWRITE访问。
    •  FILE_MAP_COPY——Copy-on-write访问。MMF对象必须具备PAGE_WRITECOPY访问。
    •  FILE_MAP_EXECUTE——执行访问。MMF对象必须具备PAGE_EXECUTE_READWRITE或PAGE_EXECUTE_READ访问。
  • dwFileOffsetHigh——映射视图在文件中的起始偏移量的DWORD高位。
  • dwFileOffsetLow——映射视图在文件中的起始偏移量的DWORD低位。
  • dwNumberOfBytesToMap——文件映射映射到视图的字节数。

  在创建了内存映射文件的视图后,可以在任何时候通过调用UnmapViewOfFile()函数来取消映射。其惟一必须的参数就是映射视图的句柄。

UnmapViewOfFile(mappedViewHandle);

  为了能够写入共享内存,首先需要使用FILE_MAP_WRITE创建MMF对象的映射视图。由于我们要在主窗口中写入该句柄,可以使用Marshal.WriteIntPtr()方法写入共享内存。写入操作完成后,需要取消映射视图,最后通过调用CloseHandle()函数释放映射视图。

public void WriteHandle(IntPtr windowHandle)
{
    IntPtr mappedViewHandle = MapViewOfFile(memoryFileHandle,
        (uint)FILE_MAP_WRITE,0,0,8);
    if(mappedViewHandle == IntPtr.Zero)
        throw new SharedMemoryException("Creating" +
            " a view of Shared Memory failed.");

    Marshal.WriteIntPtr(mappedViewHandle,windowHandle );

    UnmapViewOfFile(mappedViewHandle);
    CloseHandle((uint)mappedViewHandle);
}

  要读取共享内存,需要使用FILE_MAP_READ访问创建MMF对象的映射视图。使用Marshal.ReadIntPtr()方法来读取共享内存。完成读取操作后,取消映射视图并调用CloseHandle()函数释放映射视图。

public static IntPtr ReadHandle(string fileName)
{
    IntPtr mappedFileHandle =
      OpenFileMapping((int)FileAccess.ReadWrite, false, fileName);

    if(mappedFileHandle == IntPtr.Zero)
        throw new SharedMemoryException("Opening the" +
                    " Shared Memory for Read failed.");

    IntPtr mappedViewHandle = MapViewOfFile(mappedFileHandle,
                                        (uint)FILE_MAP_READ,0,0,8);
    if(mappedViewHandle == IntPtr.Zero)
        throw new SharedMemoryException("Creating" +
                  " a view of Shared Memory failed.");

    IntPtr windowHandle = Marshal.ReadIntPtr(mappedViewHandle);
    if(windowHandle == IntPtr.Zero)
        throw new ArgumentException ("Reading from the specified" +
                             " address in  Shared Memory failed.");

    UnmapViewOfFile(mappedViewHandle);
    CloseHandle((uint)mappedFileHandle);
    return windowHandle;
}

  当应用程序主窗口句柄创建之后,我们就将其写入共享内存。

protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated (e);
    IntPtr mainWindowHandle = this.Handle;
    try
    {
        lock(this)
        {
            //Write the handle to the Shared Memory
            sharedMemory.WriteHandle (mainWindowHandle);
        }
    }
    catch(Exception ex)
    {
        MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
             "\n\n"+ "Application Exiting...","Exception thrown");
        Application.Exit();
    }
}

  当用户尝试启动应用程序的第二个实例时,从共享内存中可以得到前一个实例的窗口句柄,并使用ShowWindow()和UpdateWindow()函数恢复主窗口。

// 如果mutex已经存在,不需要启动应用程序的新实例,
// 因为前一个实例已经在运行了。
try
{
    // 获取程序主窗口的句柄,
    // 该句柄由前一个实例存储到共享内存中。
    IntPtr mainWindowHandle = System.IntPtr.Zero;
    lock(typeof(AnimatedWindowForm))
    {
        mainWindowHandle = MemoryMappedFile.ReadHandle("Local" +
                                "file://sharedmemoryanimatedwindow/");
    }

    if(mainWindowHandle != IntPtr.Zero)
    {
        // Restore the Window
        ShowWindow(mainWindowHandle,SW_RESTORE);
        UpdateWindow(mainWindowHandle);
    }
}
catch(Exception ex)
{
    MessageBox.Show (ex.Message+ "\n\n"+ex.StackTrace+
           "\n\n"+"Application Exiting...","Exception thrown");
}

  因此我们的应用程序的main方法看起来是下面这样的:

static void Main()
{
    // Used to check if we can create a new mutex
    bool newMutexCreated = false;
    // The name of the mutex is to be prefixed with Local\ to make
    // sure that its is created in the per-session
    // namespace, not in the global namespace.
    string mutexName = "Local\\" +
      System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

    Mutex mutex = null;
    try
    {
        // Create a new mutex object with a unique name
        mutex = new Mutex(false, mutexName, out newMutexCreated);
    }
    catch(Exception ex)
    {
        MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
           "\n\n"+"Application Exiting...","Exception thrown");
        Application.Exit ();
    }

    // When the mutex is created for the first time
    // we run the program since it is the first instance.
    if(newMutexCreated)
    {
        // Create the Shared Memory to store the window
        // handle. This memory is shared between processes
        lock(typeof(AnimatedWindowForm))
        {
            sharedMemory = MemoryMappedFile.CreateMMF("Local" +
              "file://sharedmemoryanimatedwindow/",
              MemoryMappedFile.FileAccess .ReadWrite ,8);
        }
        Application.Run(new AnimatedWindowForm());
    }
    else
    // If the mutex already exists, no need to launch
    // a new instance of the program because
    // a previous instance is running .
    {
        try
        {
        // Get the Program's main window handle,
        // which was previously stored in shared memory.
            IntPtr mainWindowHandle = System.IntPtr.Zero;
            lock(typeof(AnimatedWindowForm))
            {
                mainWindowHandle =
                  MemoryMappedFile.ReadHandle("Local" +
                  "file://sharedmemoryanimatedwindow/");
            }
            if(mainWindowHandle != IntPtr.Zero)
            {
                // Restore the Window
                ShowWindow(mainWindowHandle,SW_RESTORE);
                UpdateWindow(mainWindowHandle);
            }
            return;
        }
        catch(Exception ex)
        {
            MessageBox.Show (ex.Message+"\n\n"+ex.StackTrace+
              "\n\n"+"Application Exiting...","Exception thrown");   
        }
        // Tell the garbage collector to keep the Mutex alive
        // until the code execution reaches this point,
        // ie. normally when the program is exiting.
        GC.KeepAlive(mutex);
        // Release the Mutex
        try
        {
            mutex.ReleaseMutex();
        }
        catch(ApplicationException ex)
        {
            MessageBox.Show (ex.Message + "\n\n"+ ex.StackTrace,
                                            "Exception thrown");   
            GC.Collect();
        }
    }
}

将窗口最小化到通知区域

  这包含四个任务:

  第一步是防止用户单击关闭按钮时关闭窗口,重写protected virtual OnClosing方法,取消Close事件。窗体应该被隐藏,而应用程序在后台运行。但当用户关闭系统时呢?操作系统会像所有打开着的窗口发送Close消息。如果我们的应用程序拒绝关闭窗口,系统将无法关闭,它会持续等待,直到所有窗口都关闭。因此我们需要重写WndProc需方法,处理WM_QUERYENDSESSION消息。

protected override void OnClosing(CancelEventArgs e)
{
    if(systemShutdown == true)
        e.Cancel = false;
    else
    {
        e.Cancel = true;
        this.AnimateWindow();
        this.Visible = false;
    }
}

protected override void WndProc(ref Message m)
{
    // 一旦程序收到WM_QUERYENDSESSION消息,
    // 将systemShutdown布尔值设置为true。
   
    if(m.Msg == WM_QUERYENDSESSION)
        systemShutdown = true;
    base.WndProc(ref m);
}

  接下来,我们希望在任务栏的通知区域显示一个通知图标。向主窗体添加一个NotifyIcon控件并为其设置图标。该图标将会显示在任务栏的通知区域中。我们的下一个目的是实现窗口向通知区域靠拢的动画。在做这个动画之前,我们需要确保用户没有禁用系统中的窗口动画。用户可以通过设置HKeyCurrentUser\Control Panel\Desktop下的MinAnimate键来启用/禁用窗口动画。我们检查这个值,并根据用户的偏好来设置一个布尔值。

RegistryKey animationKey =
  Registry.CurrentUser.OpenSubKey("Control Panel" +
  "file://desktop//WindowMetrics%22,true);
object animKeyValue = animationKey.GetValue("MinAnimate");
           
if(System.Convert.ToInt32 (animKeyValue.ToString()) == 0)
    this.AnimationDisabled = true;
else
this.AnimationDisabled = false;

  如果可以使用动画,我们使用DrawAnimatedRects(IntPtr hwnd, int idAni, ref RECT lprcFrom, ref RECT lprcTo)函数来绘制窗口动画。该函数有四个参数。hwnd是要进行动画的窗口句柄。idAni是动画的类型。如果指定为IDANI_CAPTION,则窗口标题会以动画方式从lprcFrom指定的位置移动到lprcTo指定的位置。否则它会绘制一个外框矩形并对其进行动画。lprcFrom和lprcTo都是RECT类型的,指定了动画的起止矩形。我们使用GetWindowRect(IntPtr hwnd, ref RECT lpRect)函数从窗体的句柄获取其矩形。最小化时,起始位置是窗口的RECT。而终止位置是通知区域的RECT。所以下一个任务是获取通知区域的句柄。任务栏的类名字是Shell_TrayWnd。任务栏包含很多其他子窗口。我们需要“notification area”的句柄,其中包含了通知图标。我们可以通过枚举Shell_TrayWnd的子窗口来获取其句柄。现在我们就可以使用GetWindowRect(IntPtr hwnd, ref RECT lpRect)函数来获取通知区域的RECT了。

private void AnimateWindow()
{
    // if the user has not disabled animating windows...
    if(!this.AnimationDisabled)
    {
        RECT animateFrom = new RECT();
        GetWindowRect(this.Handle, ref animateFrom);

        RECT animateTo = new RECT ();
        IntPtr notifyAreaHandle = GetNotificationAreaHandle();

        if (notifyAreaHandle != IntPtr.Zero)
        {
            if ( GetWindowRect(notifyAreaHandle, ref animateTo) == true)
            {
                DrawAnimatedRects(this.Handle,
                     IDANI_CAPTION,ref animateFrom,ref animateTo);
            }
        }
    }
}

private IntPtr GetNotificationAreaHandle()
{
    IntPtr hwnd = FindWindowEx(IntPtr.Zero,IntPtr.Zero,"Shell_TrayWnd",null);
    hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"TrayNotifyWnd",null);
    hwnd = FindWindowEx(hwnd , IntPtr.Zero ,"SysPager",null);
   
    if (hwnd != IntPtr.Zero)
        hwnd = FindWindowEx(hwnd , IntPtr.Zero ,null,"Notification Area");

    return hwnd;       
}

结论

  诚然,获取通知区域的窗口句柄有的时候会失败,因为“TrayNotifyWnd”、“SysPager”和“Notification Area”都是undocumented(非编档)的窗口类名,可能在未来的Windows版本中有所变化。

已知问题

  在应用程序的Debug版本和Release版本之间存在着一个冲突。如果首先启动了Release版本,然后用户再启动Debug版本,则两个实例都会运行。Mutex不能在开始时组织第二个实例的起动。

posted @ 2008-05-04 13:56 Andrew Yin 阅读(411) | 评论 (0)编辑 编辑

    首先请大家不要被我的标题唬住,关于AOP我是知之甚少的,只是对我所知道的这一些东西有些想法而已,缭表于此,博各位一笑。

    AOP,面向方面编程,关注各种程序构造中的横切面。AOP主要的实现手段是代码注入,即由编译器负责在编译过程中、生成代码之前将一个切面代码注入到用户代码中的指定位置,这些位置通常是用户代码的最前面和最后面。前些天听熊节讲述J2EE,在提到AOP时介绍了一个典型的例子——事务型的业务逻辑。在编写一个事务型的业务逻辑时,通常的做法是首先OpenTransact,然后编写业务逻辑,最后Commit;如果期间出现错误还要Rollback。很明显这样产生了非常冗余的代码,作为一个开发者,可能只注重其中的业务逻辑代码,因为事务的处理代码在各个逻辑模块中都是相同的。因此人们想到,通过特殊的标记,指定编译器来自动生成这些事务处理代码,而一个逻辑模块中仅出现业务逻辑代码。这样,AOP诞生了。

    以上的讲解是粗浅的,甚至不十分准确。但我所想到的仅仅是这个所谓的“方面”和代码的“注入”,事实上我最想说的就是,原来AOP的思想是由来已久的!

    我没有经历过打孔纸带的年月,也没有用汇编语言写过什么东西,不过还是有听过一些关于大型机的故事,并且也看过一些汇编代码的。纸带我们就不提了,首先来看看汇编语言。

    最古老的汇编语言可以被称为“指令影射语言”,这个名字是我自己起的,为什么呢?因为最早的时候一条汇编语言仅对应一条机器指令,它仅仅是机器语言的“助记符”(注意这里的“仅仅”一词);这也就意味着一个程序最终有多少条机器指令,在编写的时候我们就要写多少条,并且很多跳转地址是需要我们手工计算的。

    随后很多不甘于现状的聪明的程序员们发明了一种称之为“宏汇编”的语言,可以大大降低代码的长度,并且提高了可读性。“宏”的意义就是一条汇编语句可以生成多条机器指令。这是如何做到的呢?原来这群人仔细观察了现有的代码,发现其中存在着“模式”!比如跳转指令的用法,大部分(甚至所有)情况下的用法是通过对条件进行判断,有选择地执行两个语句块中的一个(注意不是多个语句块中的一个);于是,他们通过一种特殊的语法来形成类似于后来的高级语言中if语句的语言形态,并且由编译器在预处理过程中计算具体的跳转地址并“注入”跳转语句。

    宏汇编还有一个特性,就是关于子过程的调用。最早的时候,子过程的编写非常混乱。由于子过程往往需要参数,于是有用寄存器传递参数的、有用RAM单元传递的。慢慢人们就发现用RAM,并且是用堆栈的方式来传递参数非常方便,可以将寄存器解放出来完成更高效率的操作——于是乎,人们广泛地使用堆栈进行参数传递;再后来慢慢地人们又发现,每次调用子过程的时候,我们做的事情都是push各个参数,然后call子过程。啊哈,这时又轮到聪明的程序员们登场了,他们通过一些手段实现了类似MASM中的invoke语句,通过给定子过程的原型,使用单条语句即可完成子过程的调用。同样是由编译器将push参数和call子过程的代码“注入”到用户的宏汇编代码中。

    当然,要想令汇编语言更清晰,其实还有很多代码是可以由编译器自动生成的;然而,如果构建更复杂的宏汇编语法来实现这些功能,会使得代码反而不易阅读;因此,高级语言出现了。

    这里所谓的高级语言当然远没有今天的语言那么高级,无非就是Fortran、Ada、Cobol等,也可以说是过程语言。面向过程的语言实际上是在大量地完成着很多“模式”的汇编代码的生成,当然不乏一些更为高级的特性,如多道条件语句、存储器(变量)的控制等。

    可以认为过程语言是更好的宏汇编语言罢,当然其自然的数学表达式(如a+b这样的)则是汇编语言望尘莫及的。于是乎大家都转向了高级语言的学习和使用。而几乎所有的高级语言都提供了“结构(类似C语言的struct)”这种语言元素,因此人们慢慢又发现,他们写的程序依然有“模式”:几乎所有的程序都是“结构+结构上的操作“;几乎每次操作一个新的结构都是:首先为结构分配地址并得到其指针,调用用于初始化该结构的函数,使用其他函数对其操作,不再使用该结构的时候调用清理函数来释放其内存和由其分配的其他资源(整个过程如下图所示)。并且,初始化和清除对于每个同类的结构来说都是相同的,而操作往往是不同的,但是我们关心的又是如何操作一个结构,往往忽视其初始化和清除。

    于是乎,又有聪明人在想办法了:可不可以由编译器来生成这些初始化代码呢?其实,读这篇文章的你更聪明,这不是AOP中经典的横切镜头么,而实现的方法却正是现在广为流行的面向对象方法么!是这样,于是出现了面向对象的程序设计语言,其典型特征就是引入了class这种语言元素和object的概念,而“对象”首先处理的就是初始化和清除问题,请看下图:

    想必大家已经再熟悉不过了,用构造函数和析构函数来进行对象的初始化和清除操作;最矛盾的一点就是,构造函数和析构函数只需编写却无需调用。相信学过面向对象程序设计的朋友早已经知道,编译器会将构造函数的代码“注入”可执行代码(对于静态分配的对象)或将其调用代码注入到分配存储空间的时候;析构函数也一样,编译器将其代码或对其进行的调用“注入”到对象结束其生存期的时刻。

    接下来的事情我就不细说了。Java出现了,AOP成了和OOP平等的名词……

    文章结束得很潦草。因为我已经不知道我要传达什么思想了……总之本文就AOP中的关注点和代码注入在其他语言中找到了相似体,感到任何新技术都不是突然冒出来的,而是需要长期的积累和观察。

posted @ 2008-05-04 13:47 Andrew Yin 阅读(25) | 评论 (0)编辑 编辑

微软的DotNet开发绝对是属于那种入门容易提高难的技术。而要能够成为DotNet架构师没有三年或更长时间的编码积累基本上是不可能的。特别是在大型软件项目中,架构师是项目核心成员,承上启下,因此        RUP方法论也认同以架构为核心,体现4+1视图在整个软件开发过程中的重要作用。架构人员既要精通技术,又要熟悉业务,而且基本对软件生命周期各阶段的相关技术都需要有相关的积累和知识储备,而这些不经过多年的磨练是很难达到这个高度的。   

要成为一个合格的架构师首先必须是一个合格或优秀的编码人员,对于开发来讲编码始终都是最重要的一项技能,在编码过程中只要自己善于去思考和分析问题,就可以多学到很多相关的知识和技术。所以我们在开发过程中一定要注意新知识和新技术的学习,前人经验和成果的学习。编码过程中应该去思考的一些问题有:   

1.在编码过程中自己是否做单元测试,是否使用相关工具做单元测试,如果没有的话是什么原因无法把单元测试做起来?   
2.自己编码的泄露率情况,编码泄露的BUG的原因分析   
3.是否有意识的对代码进行重构,重构过程中是否引入了相关设计模式的思想?   
4.是否对C#语言的一些高级特性进行学习,如反射调用,异步处理等。   
5.是否对Remoting和WebService两种分布式技术做过研究和对比分析?   
6.是否经常研究开源项目和开源代码,如Duwamish,PetShop,NUnit,Enterprise        Library,Nant等   
7.是否对对象持久化机制和O/R        Mapping等相关技术做过相关的研究   
8.平时在编码过程中是否注重公用组件和公用类的复用和抽取   
9.自己在平时工作和学习中是否经常开发些小工具提高工作效率,巩固学习知识   


设计和编码其实是密切而不可分的,对于严格将设计和编码分开的瀑布模型一般也仅仅在大型项目中应用。而及时编码和设计分离,也不是将编码人员不需要思考,编码活动始终是一项创造性的劳动,如果否定这个观点那就代表编码过程完全不需要人员介入而可以完全自动化。因此在这里谈设计主要还是指设计人员的系统化思维能力,设计人员应该比开发人员站高一个层次来分析和思考问题。设计人员最重要的一个技能就是现实-    >抽象的转换,而这个就需要谈到方法论的问题了,技术人员需要积累面对对象分析和设计或结构化分析知识的积累,需要有较强的数据库分析和设计能力。一个设计能否成为很好的架构师关键就在这种积累的深度和广度上面了。   

因此在设计过程中应该考虑的问题有:   
1.你现在分析和设计能力能否胜任大中型的应用系统还是只是独立功能分析和设计?   
2.设计过程中是否有意识的考虑到组件的复用和相关接口设计准则。是否能够很自然的将分析模式,设计模式的相关内容应用到自己的设计过程中。   
3.是否对XP,RUP,面向对象,结构化等方法论都有过较系统化的学习和思考。   
4.是否真正理解系统功能需求和非功能需求对系统设计的不同的指导作用。   
5.对自己设计的功能是否会根据后期的变更来反思自己的设计为何不能很好的适应变更?   
6.是否在设计过程中经常自己开发些原型来对自己的设计思路进行验证?   
7.是否专注技术的同时开始专业业务流程的分析,关注业务建模?   


如果我们在设计和开发过程中经常关注这些知识和技能的话,成为一个合格的架构师是早晚的事情。平时能够胜任工作开发用到的知识和技能是微不足道的,如果自己不是有意识的去学习这些知识的话,那技能是很难得到进一步提高的。我参加过两次微软的架构师培训,在北京的微软架构峰会上也有机会专门参加了P&P        Workshop的学习,培训老师是微软总部SmartClient        Architecture        and        Design        Guide一书的作者Edward        A.Jezieski,让我感受最深是老外深刻的技术底蕴,对程序开发的执著。   

对于DotNet架构经常用到的知识和技能储备有   
1.RUP方法论,4+1视图。用例驱动业务建模-    >分析模型-    >设计模型   
2.用例模式-    >分析模式-    >设计模式   
3.常用的分布式技术   
4.对安全,异常,日志,性能等非功能性需求的关注   
5.对应用系统整体业务的关注   

相关的一些参考书籍(微软网站和电驴都可以下载到)   

微软网站提供的参考书籍   
Enterprise        Solution        Patterns        Using        Microsoft        .NET   
.NET        Data        AccessArchitecture        Guide   
Application        Architecture        for        .NET:Designing        Applications        and        Services   
Caching        Architecture        Guide        for        .NET        Framework        Applications   
Designing        Application-Managed        Authorization   
Smart        Client        Architecture        and        Design        Guide   

其它架构方面的参考书籍   
Software        Architecture        In        Practice   
Pattern-Oriented        Software        Architecture   
The        Art        Of        Software        Architecture   
Beyond        Software        Architecture   

模式方面的书籍   
Analysis        Patterns   
Design        Patterns        -        Elements        of        Reusable        Object-Oriented        Software   
Applying        UML        and        Patterns   
Design        Pattern*        **plained

posted @ 2008-05-04 13:25 Andrew Yin 阅读(29) | 评论 (0)编辑 编辑