Observer模式稍稍有点复杂,但是由于他的作用如此强大,所以几乎在所有的带有分层概念的系统中都必须使用。
带有GUI的软件几乎都有分层的概念,界面是一层,逻辑实现是一层。所以他们也就成了最常见的Observer模式的例子。
不过Observer模式的用处要比单纯的GUI要宽广很多。Observer的应用环境是,有上下两层模块,下层模块的数据会自发变化,上层模块必须响应这样的变化,但是上层模块不知道下层模块会在什么时候变化。这种情况下就应该使用Observer模式,上层模块让下层模块知道自己的存在,然后在下层模块在变化的时候通知上层模块。
Observer模式的另一个比较有趣的别名是Hollywood模式,因为这个模式可以被描述为“Don't call me, I'll call you.”。
由于Observer如此的常用,所以在实际的使用过程中,这已经是一个烂熟于心的模式。《ASD》一书在讲述这个模式的时候,也没有多少新意,但是还让我有一些点滴的感想:
- Observer模式是典型的由DIP原则驱动的模式。无论是上层数据的变化还是界面的显示都是具体类。具体类应该是不稳定的,不能被依靠。所以必须抽象出接口,就是下层的Subject接口和上层的Observer接口。Subject接口依赖Observer接口。
- 根据单一职责原则(SRP)和接口分离原则(ISP),Subject应该只是处理register和notify的任务,而和具体逻辑无关。这在使用拉模式时尤其需要注意。
- Observer模式有推模式和拉模式两种方法。推模式中,由Subject把数据直接告诉Observer。在拉模式种,Subject只是告诉Observer有事情在变化,然后由Observer来查询是什么数据变化了。究竟使用何种模式应该视具体应用而定。然而常常是两个模式的混合运用。
- 在使用拉模式的时候,Observer要向下层询问获取数据。然而数据属于具体的业务逻辑,和Observer模式的框架无关。因此这个接口不应该被包含在Subject接口中,而需要设立一个单独的业务接口。也就是说,在这种情况下,下层模块要继承两个接口,Subject接口和自己的业务逻辑相关的接口。(如果不这么作,将会形成依赖环)
- 如果把推模式和拉模式混合使用,即是,Subject告诉Observer一个消息,提示是何种变化。Observer判断是否要查询更具体的数据,或者如何查询。这就是消息驱动系统了。
- 另外一种变化是,Observer在注册的时候,就表明自己对何种消息感兴趣。Subject在事件发生的时候,只是通知感兴趣的Observer。
2005年3月4日星期五
2005年3月3日星期四
《ASD》设计模式:Composite
我对于Comosite模式有种特殊的感情,因为这是我第一个理解了的模式。之前看《设计模式》一直未能理解模式的意义,直到看到Comopiste,才觉得豁然开朗。之后再看其它模式,也觉得能够理解模式背后的意图了。

Composite模式其实是一种非常特别的模式,因为他所关注的是结构,而较少关注行为。这与大部分的设计模式是有所不同的。
可是,Composite模式的结构是非常精巧的,甚至可以称之为美丽动人。
元素合在一起组成了结构。结构可以和零散的元素或者别的结构再次组合,形成新的结构。这样的组合可以无穷尽地延续下去。
常常,我们需要的是最上层的整体。
Composite模式让我们可以在运行时期,用组合的方式来建造这样的整体。
Composite模式较少被误用。因为她的应用总是在特别的面向数据结构的上下文环境。而且也没有多少别的方法可以顶替Composite的作用。
Composite模式不太关注行为,即如何使用这个巨大组合结构中的元素。她本身只提供了很简单的深度优先的遍历方法。
如果有更高的行为上的要求,就要借助于另外两个模式:iterator和vistor。
Composite模式其实是一种非常特别的模式,因为他所关注的是结构,而较少关注行为。这与大部分的设计模式是有所不同的。
可是,Composite模式的结构是非常精巧的,甚至可以称之为美丽动人。
元素合在一起组成了结构。结构可以和零散的元素或者别的结构再次组合,形成新的结构。这样的组合可以无穷尽地延续下去。
常常,我们需要的是最上层的整体。
Composite模式让我们可以在运行时期,用组合的方式来建造这样的整体。
Composite模式较少被误用。因为她的应用总是在特别的面向数据结构的上下文环境。而且也没有多少别的方法可以顶替Composite的作用。
Composite模式不太关注行为,即如何使用这个巨大组合结构中的元素。她本身只提供了很简单的深度优先的遍历方法。
如果有更高的行为上的要求,就要借助于另外两个模式:iterator和vistor。
See The Future. Make History.
今明两天,在Redmond有微软一年一届的TechFest。微软在全世界的六个研究院(Redmond,湾区,硅谷,剑桥,北京,印度)都会在这个TechFest上,展示自己一年来的成果。See the future. Makes history就是本届TechFest的口号。
TechFest在三个大会议室中,布置了很多摊位,每个摊位上都有一个技术展示。另外还有两个大会议室,用来作一小时一个的报告。
我今天去的稍晚了一点,到场的时候已经快要4:30PM了。没有去听报告,直接去展厅看技术展示。有很多展示都非常让人兴奋,花了一个半小时,只是马马虎虎地看完了一个展厅。剩下的两个展厅就已经关门了。
只能暗下决心,明天一定要早点去。
我拿不准今天看到的东西中,有多少是可以公开谈论的。所以只好不谈具体细节,只说说大致内容。
在今天这个展厅中,展示的技术,主要集中在系统与网络、软件工程、数据库、P2P方面。身为程序员,当然是软件工程方面的技术最有切身关系。有几个项目,和单元测试、源码数据挖掘、编程框架相关的,让我觉得特别的兴奋。真的是非常希望能够快点用上他们的成果。
TechFest在三个大会议室中,布置了很多摊位,每个摊位上都有一个技术展示。另外还有两个大会议室,用来作一小时一个的报告。
我今天去的稍晚了一点,到场的时候已经快要4:30PM了。没有去听报告,直接去展厅看技术展示。有很多展示都非常让人兴奋,花了一个半小时,只是马马虎虎地看完了一个展厅。剩下的两个展厅就已经关门了。
只能暗下决心,明天一定要早点去。
我拿不准今天看到的东西中,有多少是可以公开谈论的。所以只好不谈具体细节,只说说大致内容。
在今天这个展厅中,展示的技术,主要集中在系统与网络、软件工程、数据库、P2P方面。身为程序员,当然是软件工程方面的技术最有切身关系。有几个项目,和单元测试、源码数据挖掘、编程框架相关的,让我觉得特别的兴奋。真的是非常希望能够快点用上他们的成果。
2005年3月2日星期三
相声瓦舍
我用eMule下载了一些台湾相声瓦舍表演的相声段落。听了之后,觉得非常过瘾。
相声瓦舍中,最重要的两个表演者当属冯翊君和宋少卿二人。感觉上这两人的语言基本功依然不如大陆的一流相声演员,再表演报菜名这样的传统段子的时候,虽然还算精彩,但是就远不如大陆相声演员的神乎其技。
可是谁喜欢看一个人站在台上口沫四溅的念绕口令?
相声瓦舍最让人觉得过瘾的是他们的讽刺态度。讽刺时政,也讽刺不良的国民性。例如《相声说跨鬼子》系列,听来实在过瘾。
然而有些段子,虽然没有太强烈的讽刺意味,例如《战国厕》,但是想像之丰富,也是大陆的相声中所见不到的。
在创作的时候,束手束脚,这也不能,那也不能,久而久之,连正常的想像力也都没有了。大陆的相声创作大概就是如此。
之前,听过一个一米线的理论。
大意是说,有一个限制在那里,谁都知道不能跨过那根线,否则就很糟糕了。
可是这样是不够的。编辑还很担心,自己会失手放过什么过界的东西吗。所以编辑会在那个限制之前,退出一米再划根线。
然后是作者。他们会在编辑的那根线之前,再退出一米,再划一根线。
可实际上,并不只有这三根线。
因为所有的人都不确定自己的那根线是否划对了。看到别人的线好像更远一点,自己也就再退一点。
于是你退一米,我退一米。那根线就越退越远了。
真正的问题不是在于那根线在哪里,而是在于有那么一根线,人就失去了一种自由的能力。
这就是所谓的精神阉割。
所以,听听相声瓦舍,听听没有被阉割过的声音,真的很过瘾啊。
相声瓦舍中,最重要的两个表演者当属冯翊君和宋少卿二人。感觉上这两人的语言基本功依然不如大陆的一流相声演员,再表演报菜名这样的传统段子的时候,虽然还算精彩,但是就远不如大陆相声演员的神乎其技。
可是谁喜欢看一个人站在台上口沫四溅的念绕口令?
相声瓦舍最让人觉得过瘾的是他们的讽刺态度。讽刺时政,也讽刺不良的国民性。例如《相声说跨鬼子》系列,听来实在过瘾。
然而有些段子,虽然没有太强烈的讽刺意味,例如《战国厕》,但是想像之丰富,也是大陆的相声中所见不到的。
在创作的时候,束手束脚,这也不能,那也不能,久而久之,连正常的想像力也都没有了。大陆的相声创作大概就是如此。
之前,听过一个一米线的理论。
大意是说,有一个限制在那里,谁都知道不能跨过那根线,否则就很糟糕了。
可是这样是不够的。编辑还很担心,自己会失手放过什么过界的东西吗。所以编辑会在那个限制之前,退出一米再划根线。
然后是作者。他们会在编辑的那根线之前,再退出一米,再划一根线。
可实际上,并不只有这三根线。
因为所有的人都不确定自己的那根线是否划对了。看到别人的线好像更远一点,自己也就再退一点。
于是你退一米,我退一米。那根线就越退越远了。
真正的问题不是在于那根线在哪里,而是在于有那么一根线,人就失去了一种自由的能力。
这就是所谓的精神阉割。
所以,听听相声瓦舍,听听没有被阉割过的声音,真的很过瘾啊。
2005年2月28日星期一
Hike of Rattlesnake Mountain
今天和几个中国同事去爬Rattlesnkae Mountain。似乎这是我去年冬天以来的第一次hiking。
Seatllte附近多山,因此也有颇多的登山爱好者。连带着,也就有了很多供hiking用的trail。随便一个小山丘或者湖边,都有一两条trail。
在国内,北京附近爬山,常常是要么没有路,要么路边全是卖东西的。
而在这里,风格却很不同。到处有专门供人远足用的小路,但是没有什么人卖东西。似乎也没有什么人来维护。小路两边没有垃圾桶,也不会有人把垃圾扔在路边。感觉这里的大部分地方都已经被开发过了,但是又保持着一种很自然的面貌。
对于爱好爬山的人来说,这里确实是一个非常惬意的所在。
Rattlesnkae Mountain应该算是比较有名的爬山去处。今天爬上的一路上,见到了不少人。(相对于这里别的地方而言,和北京香山的人海是完全不能比的了)。很多人都带着狗。也有几个人背着小孩。
这次hiking的路程很短,只有2 miles,合3.2公里。不过等上山峰以后,视野到也是非常开阔。
最遗憾的是,我虽然带了DC上山,但是居然忘了带存储卡。所以今天这里附带的照片都是我的朋友Jun拍的。Jun的照片拍得非常漂亮,大家可以去他的blog看看。

照片链接:Rattlesnake Mountain
Seatllte附近多山,因此也有颇多的登山爱好者。连带着,也就有了很多供hiking用的trail。随便一个小山丘或者湖边,都有一两条trail。
在国内,北京附近爬山,常常是要么没有路,要么路边全是卖东西的。
而在这里,风格却很不同。到处有专门供人远足用的小路,但是没有什么人卖东西。似乎也没有什么人来维护。小路两边没有垃圾桶,也不会有人把垃圾扔在路边。感觉这里的大部分地方都已经被开发过了,但是又保持着一种很自然的面貌。
对于爱好爬山的人来说,这里确实是一个非常惬意的所在。
Rattlesnkae Mountain应该算是比较有名的爬山去处。今天爬上的一路上,见到了不少人。(相对于这里别的地方而言,和北京香山的人海是完全不能比的了)。很多人都带着狗。也有几个人背着小孩。
这次hiking的路程很短,只有2 miles,合3.2公里。不过等上山峰以后,视野到也是非常开阔。
最遗憾的是,我虽然带了DC上山,但是居然忘了带存储卡。所以今天这里附带的照片都是我的朋友Jun拍的。Jun的照片拍得非常漂亮,大家可以去他的blog看看。
照片链接:Rattlesnake Mountain
2005年2月27日星期日
出游(玩具商店,华盛顿大学)
春暖花开的周末,照例出去兜风。
中午先去了Bellevue Square,看了看朋友推荐的几个特色商店,Sharper Image和Discovery Chanel Shop。这几个商店都是卖一些特别的“玩具”,例如把时间显示投影到墙上的钟,电动的DVD架,在地毯上自动游弋的吸尘器。Discovery卖的东西相对要低龄化一些,比如用来侍养蚂蚁的模拟生态环境,能显示当前天空星座的灯。
与其说是卖“玩具”,不如说是卖idea的商店。很多东西都非常对我的口味。可是论到价格,于国人而言,却已经不能当作玩具来看待了。
这些天不少地方的樱花都开了。所以我们下午去了华盛顿大学,希望能看到盛开的樱花。不过可惜的,华大里的大部分樱花树还只是含苞待放的状态,算是有点遗憾了。
对于华大,我没有什么好说的。梅贻琦先生说,大学者,乃有大师之谓也,非大楼之谓也。
我只是看了看华大的大楼,当然是什么也说不出来了。
2005年2月26日星期六
《ASD》设计模式:Factory - 兼谈模式的滥用
operator new是个很特别的命令,只要一出现,就会破坏DIP(依赖倒置原则)和OCP(开放封闭原则),然而她又是不能避免的命令。
解决办法就是使用Factory模式,把new命令与使用者隔离开来。
然而依然需要一个地方来创建Facotry类的具体实例,不过这个工作可以放在应用程序的初始化部分来作,与其它应用还是隔离开来的。
关于Factory应该不用多写什么了。对于一个面向对象程序员来说,这可能是最常见的模式了。
不过可以谈谈什么时候该用Factory模式。
原则上说,当创建一个可能会变化的具体对象时,就应该(必须)要使用Factory模式。
然而,什么对象是会变化呢?
传统的设计方法是在最初的设计时就考虑这个问题。
可是极限编程的思想略有不同。
极限编程则认为,第一,在项目之初是很难预料哪些类是会变化的,所以不应该预先考虑这些变化。
说实话,对于设计工程师来说,要接受这个观点还真的有点痛苦。虽然我知道她说的是真的。
如果,软件不需要变化,那么面向对象的思想就失去了大半的意义。因此工程师们一定要让自己的作品能够灵活地响应变化,这也就是OCP的原则。
然而,当运用了大堆的strategy、template、factory等等模式,建造出一个叹为观止的系统后,只等着需求改变,这个系统就能体现出OCP的好处。这时就象是搭了一个完美的陷阱,只等着猎物经过。
可惜的是,猎物却常常并不从设置陷阱的地方出现。需求的改变总是出乎设计者的预想。那个陷阱成了一个完美的装饰品。
我写过很多这样的装饰品。他们唯一的用处就是给后来者作为学习的例子。
那么该在何时使用这些模式来让程序变得坚固而灵活可以承受需求的改变呢?
极限编程认为,第二,发生过变化的地方趋向于再次变化。套用一句老话,“可以跌倒,但是不可以在同一个地方跌倒”
就软件设计而言,在最初,并不去考虑应该应对什么样的变化。只有当第一次变化出现的时候,才去更改设计,让程序对这种变化能够遵守OCP的原则。并且,这种改动后的设计,应该能够应对今后出现的所有同类变化。
我想,对于设计工程师,这种观念应该是可以接受的。
设计模式的初学者,就好像拾到了一本秘籍的孩子。艺成下山,不管遇到什么都想施展一下拳脚。这就是模式的滥用。
如何避免模式的滥用,应该多考虑一下上面所说的两条原则。
解决办法就是使用Factory模式,把new命令与使用者隔离开来。
然而依然需要一个地方来创建Facotry类的具体实例,不过这个工作可以放在应用程序的初始化部分来作,与其它应用还是隔离开来的。
关于Factory应该不用多写什么了。对于一个面向对象程序员来说,这可能是最常见的模式了。
不过可以谈谈什么时候该用Factory模式。
原则上说,当创建一个可能会变化的具体对象时,就应该(必须)要使用Factory模式。
然而,什么对象是会变化呢?
传统的设计方法是在最初的设计时就考虑这个问题。
可是极限编程的思想略有不同。
极限编程则认为,第一,在项目之初是很难预料哪些类是会变化的,所以不应该预先考虑这些变化。
说实话,对于设计工程师来说,要接受这个观点还真的有点痛苦。虽然我知道她说的是真的。
如果,软件不需要变化,那么面向对象的思想就失去了大半的意义。因此工程师们一定要让自己的作品能够灵活地响应变化,这也就是OCP的原则。
然而,当运用了大堆的strategy、template、factory等等模式,建造出一个叹为观止的系统后,只等着需求改变,这个系统就能体现出OCP的好处。这时就象是搭了一个完美的陷阱,只等着猎物经过。
可惜的是,猎物却常常并不从设置陷阱的地方出现。需求的改变总是出乎设计者的预想。那个陷阱成了一个完美的装饰品。
我写过很多这样的装饰品。他们唯一的用处就是给后来者作为学习的例子。
那么该在何时使用这些模式来让程序变得坚固而灵活可以承受需求的改变呢?
极限编程认为,第二,发生过变化的地方趋向于再次变化。套用一句老话,“可以跌倒,但是不可以在同一个地方跌倒”
就软件设计而言,在最初,并不去考虑应该应对什么样的变化。只有当第一次变化出现的时候,才去更改设计,让程序对这种变化能够遵守OCP的原则。并且,这种改动后的设计,应该能够应对今后出现的所有同类变化。
我想,对于设计工程师,这种观念应该是可以接受的。
设计模式的初学者,就好像拾到了一本秘籍的孩子。艺成下山,不管遇到什么都想施展一下拳脚。这就是模式的滥用。
如何避免模式的滥用,应该多考虑一下上面所说的两条原则。
2005年2月24日星期四
《ASD》包的设计原则-兼谈极限编程
我在之前谈ASD的时候,都有意回避了开发流程的问题。然而实际上敏捷开发就是极限编程的另一个名字。
对于一个了解传统软件工程的人来说,甚至对于一个有传统项目开发经验的人来说,极限编程的观念都有点难于接受。正因为如此,我闭口不谈极限编程,只谈论设计模式。无论如何设计模式是好东西。
到了“包”的部分,极限编程的话题再也绕不开了。
极限编程中最惊世骇俗的部分大概是,反对建模,直接实现功能,然后在软件具备一定规模之后,才由下向上的设计“包”。
如果过去有人向我提出这样的观念,一定会被我B4的,但是我现在到觉得,极限编程有一定道理。至少她在规模较小的项目中是有道理的。
该如何衡量项目大小呢?这又是一个很难的问题。但是可以确定的是,大部分的项目都是小项目。
极限编程认为,在实现功能之前,先建建立全局的模型是没有意义的,这样的模型只会限制软件的开发,然后模型和实现双方互相妥协,结果模型被改得面目全非,还是离理想结果相距甚远。
反对极限编程的人认为,没有总体模型的指导,实现会缺乏大局观,变得混乱重复,难于维护。
极限编程提高维护性的主要作法就是依赖“包”。“包”并不是传统的模块的概念,而只是一个软件结构的映射,用于管理依赖和重用。
而由于测试驱动开发,软件结构是非常容易变动的,所以可以在混乱刚出现的时候,调整“包”,来恢复整个软件结构的有序。
但是如果没有总体的模型,开发人员在实现功能的时候,如何才能知道有没有可以重用的包呢?在调整软件的时候,又如何知道该调整到哪个包里去呢?
这就是说,需要有人(很多人,最好是全部开发人员)了解整个系统的情况,知道包的结构。
这也是极限编程强调结对开发(pair development)的原因之一。结对开发的要义不仅是两个人一块干活,而且还是要经常变更自己的开发伴侣,开发系统中的不同模块。这样让知识在团队中广泛传播。团队中的每个人,都对系统中的各个部分都有所认识,然后软件的灵活调整才能得以实现。
我基本上同意上诉观点。还有两个推论:
第一个推论,项目的大小取决于开发人员的能力。由上面的讨论可以看出,要实施极限编程,需要每个开发人员都了解系统的结构。但是,事实上,个人的能力有限,在有限的时间中,能够理解的系统结构也是有限的。超出了这个限制之后,极限编程大概就会暴露出越来越多的问题。所以,极限编程确实在较小的项目中比较有效,这个大小的限制即是开发人员能够理解的系统的大小。团队中开发人员平均水平越强,极限编程能够支持的系统也就越大。
推论的推论,如果是作一个个人作品,毫无疑问的应该使用极限编程。
顺便一提的是,与极限编程对应的另一种方法是软件工厂,即是由少量的精英的制定模型,大量的软件蓝领实现极细小的模块。虽然我由衷的不喜欢这个点子,但是也许这种方法更适合中国的国庆。
第二个推论,极限编程的各个部分有着密切的关系,分开来应用其中的一条两条,并非极限编程。
好了,下面来谈论包的设计原则。前面已经提到,包的主要目的是管理软件模块的依赖关系和重用。包的设计原则就是为了使这样的管理尽可能的最优化。
下面来看看这些原则。这些原则的前提是,这是一个应用极限编程的项目。即是说,项目的规模有限,并且已经有了一定规模的类,现在要用包来管理这些类。
内聚性原则:包括REP,CRP,SRP。这里主要解决的是包的粒度问题,多少类该被放进同一个包中。然而,包其实并不等于软件模块,所以包的大小在通常情况下,是远小于软件模块的,就是说,同一个模块可能被放在多个包中。
REP是重用发布等价原则,重用的粒度就是发布的粒度。即是说,一个包中的软件要么都是可重用的,要么都是不可重用的。我的理解是,实现feature时要想想,那些部分是这个feature独有的,那些部分是其它feature也可以用的。这两个部分要分入不同的包中。
CRP,共同重用原则,一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。我的理解是,即使一个feature中有很多部分都是可以重用的。但如果这些部分可以单独重用,那么也要分入不同的包。
CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其它的包不造成任何影响。我的理解是,应该把对于同一个变化敏感的类尽量放入同一个包中。
用不太准确的说法来说,前两个原则的动力来自包的client,趋向于拆分包;后一个原则的动力来自包的server,趋向于聚合包。包的粒度就是在这两种力量之间的动态平衡。需要注意的是,所谓包的client和server可能和你的想像有所不同。
耦合性原则,包括无环依赖原则,稳定依赖原则,稳定抽象原则。这些原则主要用于解决包之间的依赖关系。
无环依赖原则,在包的依赖关系图中,不允许存在环。所谓包的依赖是指,如果A包中的文件依赖B包中的文件时,就是A包依赖B包。(之前在DIP原则(类的设计原则)中,已经提到了实现应该依赖于接口。)这是一个应该严格遵守的原则,只有遵守了这个原则,才能谈后两个耦合性原则。依赖关系中如果出现了环,就要解决掉环。解决环的方法有两个,第一个方法的提示是DIP原则。第二个方法是,把两个包都依赖的部分提取出来作成第三个包,让两个包都依赖于这个包。这个方法的提示是,包在开发过程中是动态变化的。
稳定依赖原则,朝着稳定的方向进行依赖。所谓稳定性,即是包的依赖关系。若是包A依赖一大堆其它的包,那么任何一个包的变化,都会引起包A的变化,包A即是不稳定的。相反,若一大堆的包依赖包B,那么包B若要变化就会引起很多其它包的变化,这么说包B很难改变,包B即是稳定的。《ASD》中描述了一个计算不稳定性的方法,称为不稳定性度量I,主要取决于依赖于包的类的数量和包中依赖于包外的类的数量,具体算法很简单,略。I是一个比值,范围是[0,1]。0时最稳定,1时最不稳定。这个I值对于类的划分有点意义,要把不变的类放入I值较小的包,把要变的类放入I值较大的包。但是I的主要意义是表现在稳定抽象原则中。
稳定抽象原则,包的抽象程度应该和其稳定程度一致。理论上,抽象类即是接口,趋向于不变,具体类即是实现,趋向于变化。《ASD》再次给出一个描述包的抽象性度量A,即是用包中抽象类的总数除以保重类的总数得到的比值。这个抽象性A的取值范围是从0到1,数值越大,抽象性越大。因此,在理想的系统中,I越大,A就应该越小;A越大,I就应该越小。若是把A和I作成二维的坐标体系,那么(0,1)到(1,0)的连线就是一根理想连线,若是一个包远离这个连线,那么这个包的设计就是有问题的。
这个让我想到,也许应该设计一个软件来自动管理包的A值和I值,自动给出统计数据。那么对于系统的设计应该会有很大的好处。也许,过一段时间,等我手上的项目不太多的时候,我也许可以写一个这样的东西出来。
一个问题,在C++中,如何表现“包”呢?不明白,要想办法查查看。
对于一个了解传统软件工程的人来说,甚至对于一个有传统项目开发经验的人来说,极限编程的观念都有点难于接受。正因为如此,我闭口不谈极限编程,只谈论设计模式。无论如何设计模式是好东西。
到了“包”的部分,极限编程的话题再也绕不开了。
极限编程中最惊世骇俗的部分大概是,反对建模,直接实现功能,然后在软件具备一定规模之后,才由下向上的设计“包”。
如果过去有人向我提出这样的观念,一定会被我B4的,但是我现在到觉得,极限编程有一定道理。至少她在规模较小的项目中是有道理的。
该如何衡量项目大小呢?这又是一个很难的问题。但是可以确定的是,大部分的项目都是小项目。
极限编程认为,在实现功能之前,先建建立全局的模型是没有意义的,这样的模型只会限制软件的开发,然后模型和实现双方互相妥协,结果模型被改得面目全非,还是离理想结果相距甚远。
反对极限编程的人认为,没有总体模型的指导,实现会缺乏大局观,变得混乱重复,难于维护。
极限编程提高维护性的主要作法就是依赖“包”。“包”并不是传统的模块的概念,而只是一个软件结构的映射,用于管理依赖和重用。
而由于测试驱动开发,软件结构是非常容易变动的,所以可以在混乱刚出现的时候,调整“包”,来恢复整个软件结构的有序。
但是如果没有总体的模型,开发人员在实现功能的时候,如何才能知道有没有可以重用的包呢?在调整软件的时候,又如何知道该调整到哪个包里去呢?
这就是说,需要有人(很多人,最好是全部开发人员)了解整个系统的情况,知道包的结构。
这也是极限编程强调结对开发(pair development)的原因之一。结对开发的要义不仅是两个人一块干活,而且还是要经常变更自己的开发伴侣,开发系统中的不同模块。这样让知识在团队中广泛传播。团队中的每个人,都对系统中的各个部分都有所认识,然后软件的灵活调整才能得以实现。
我基本上同意上诉观点。还有两个推论:
第一个推论,项目的大小取决于开发人员的能力。由上面的讨论可以看出,要实施极限编程,需要每个开发人员都了解系统的结构。但是,事实上,个人的能力有限,在有限的时间中,能够理解的系统结构也是有限的。超出了这个限制之后,极限编程大概就会暴露出越来越多的问题。所以,极限编程确实在较小的项目中比较有效,这个大小的限制即是开发人员能够理解的系统的大小。团队中开发人员平均水平越强,极限编程能够支持的系统也就越大。
推论的推论,如果是作一个个人作品,毫无疑问的应该使用极限编程。
顺便一提的是,与极限编程对应的另一种方法是软件工厂,即是由少量的精英的制定模型,大量的软件蓝领实现极细小的模块。虽然我由衷的不喜欢这个点子,但是也许这种方法更适合中国的国庆。
第二个推论,极限编程的各个部分有着密切的关系,分开来应用其中的一条两条,并非极限编程。
好了,下面来谈论包的设计原则。前面已经提到,包的主要目的是管理软件模块的依赖关系和重用。包的设计原则就是为了使这样的管理尽可能的最优化。
下面来看看这些原则。这些原则的前提是,这是一个应用极限编程的项目。即是说,项目的规模有限,并且已经有了一定规模的类,现在要用包来管理这些类。
内聚性原则:包括REP,CRP,SRP。这里主要解决的是包的粒度问题,多少类该被放进同一个包中。然而,包其实并不等于软件模块,所以包的大小在通常情况下,是远小于软件模块的,就是说,同一个模块可能被放在多个包中。
REP是重用发布等价原则,重用的粒度就是发布的粒度。即是说,一个包中的软件要么都是可重用的,要么都是不可重用的。我的理解是,实现feature时要想想,那些部分是这个feature独有的,那些部分是其它feature也可以用的。这两个部分要分入不同的包中。
CRP,共同重用原则,一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。我的理解是,即使一个feature中有很多部分都是可以重用的。但如果这些部分可以单独重用,那么也要分入不同的包。
CCP,共同封闭原则,包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其它的包不造成任何影响。我的理解是,应该把对于同一个变化敏感的类尽量放入同一个包中。
用不太准确的说法来说,前两个原则的动力来自包的client,趋向于拆分包;后一个原则的动力来自包的server,趋向于聚合包。包的粒度就是在这两种力量之间的动态平衡。需要注意的是,所谓包的client和server可能和你的想像有所不同。
耦合性原则,包括无环依赖原则,稳定依赖原则,稳定抽象原则。这些原则主要用于解决包之间的依赖关系。
无环依赖原则,在包的依赖关系图中,不允许存在环。所谓包的依赖是指,如果A包中的文件依赖B包中的文件时,就是A包依赖B包。(之前在DIP原则(类的设计原则)中,已经提到了实现应该依赖于接口。)这是一个应该严格遵守的原则,只有遵守了这个原则,才能谈后两个耦合性原则。依赖关系中如果出现了环,就要解决掉环。解决环的方法有两个,第一个方法的提示是DIP原则。第二个方法是,把两个包都依赖的部分提取出来作成第三个包,让两个包都依赖于这个包。这个方法的提示是,包在开发过程中是动态变化的。
稳定依赖原则,朝着稳定的方向进行依赖。所谓稳定性,即是包的依赖关系。若是包A依赖一大堆其它的包,那么任何一个包的变化,都会引起包A的变化,包A即是不稳定的。相反,若一大堆的包依赖包B,那么包B若要变化就会引起很多其它包的变化,这么说包B很难改变,包B即是稳定的。《ASD》中描述了一个计算不稳定性的方法,称为不稳定性度量I,主要取决于依赖于包的类的数量和包中依赖于包外的类的数量,具体算法很简单,略。I是一个比值,范围是[0,1]。0时最稳定,1时最不稳定。这个I值对于类的划分有点意义,要把不变的类放入I值较小的包,把要变的类放入I值较大的包。但是I的主要意义是表现在稳定抽象原则中。
稳定抽象原则,包的抽象程度应该和其稳定程度一致。理论上,抽象类即是接口,趋向于不变,具体类即是实现,趋向于变化。《ASD》再次给出一个描述包的抽象性度量A,即是用包中抽象类的总数除以保重类的总数得到的比值。这个抽象性A的取值范围是从0到1,数值越大,抽象性越大。因此,在理想的系统中,I越大,A就应该越小;A越大,I就应该越小。若是把A和I作成二维的坐标体系,那么(0,1)到(1,0)的连线就是一根理想连线,若是一个包远离这个连线,那么这个包的设计就是有问题的。
这个让我想到,也许应该设计一个软件来自动管理包的A值和I值,自动给出统计数据。那么对于系统的设计应该会有很大的好处。也许,过一段时间,等我手上的项目不太多的时候,我也许可以写一个这样的东西出来。
一个问题,在C++中,如何表现“包”呢?不明白,要想办法查查看。
2005年2月19日星期六
Everything About Everything
前天我工作所用的Windows忽然不能启动,重装系统也告失败,所以就只好向IT部门求助了。
IT部门的兄弟说,尽快给我送个哥们来帮忙。
于是我就一直待在办公室里等待。可是,这两天,恰好,IT兄弟们赶上了农忙季节。竟然把我凉拌了一天也没有人来理我。
于是,我只好在办公室里看文档。把之前想看而没有时间看的文档全读完了。
之后开始读书,一口气读完了五章《ASD》,连读书笔记都写了三篇。
晚上回家的时候,在路上听iPod,听到了《the Big Blue》配乐二。一开始穿插了很多电影中的台词。
Enzo声音低沉,磁力无限地说:“welcome to blue”。
这几天来,每日的天空都干净得耀眼,晚上星空灿烂。
我已经熟悉了北斗星和猎户星座。昨夜,在回家的时候,又凭着中学时代的记忆,顺着北斗七星,在北方天空中找出了北极星。
发现每日上班走的148路,几乎是完完全全的正南正北指向。
iPod中,传出了Enzo和Jacque的对话。
What do you want to know?
Everything.
Everything about what?
Everything about everything.
IT部门的兄弟说,尽快给我送个哥们来帮忙。
于是我就一直待在办公室里等待。可是,这两天,恰好,IT兄弟们赶上了农忙季节。竟然把我凉拌了一天也没有人来理我。
于是,我只好在办公室里看文档。把之前想看而没有时间看的文档全读完了。
之后开始读书,一口气读完了五章《ASD》,连读书笔记都写了三篇。
晚上回家的时候,在路上听iPod,听到了《the Big Blue》配乐二。一开始穿插了很多电影中的台词。
Enzo声音低沉,磁力无限地说:“welcome to blue”。
这几天来,每日的天空都干净得耀眼,晚上星空灿烂。
我已经熟悉了北斗星和猎户星座。昨夜,在回家的时候,又凭着中学时代的记忆,顺着北斗七星,在北方天空中找出了北极星。
发现每日上班走的148路,几乎是完完全全的正南正北指向。
iPod中,传出了Enzo和Jacque的对话。
What do you want to know?
Everything.
Everything about what?
Everything about everything.
〖转贴〗中国人的种族观念
【若昔难得 按:】写这文章的人是个爱尔兰老外。于国人而言,对于他的指针,可能有很多都会断然否决,或曰以偏概全。
也许是以偏概全,也有可能是旁观者清。
中国正在迅速地走向世界。之前,中国较少接触外面的世界,所以种族观念并未彰显。
而现在,与外界的联系日益加强,中国之国力也与日渐进,种族问题确实是值得国人注意。
就该文所讲的具体实例,在我自己的身上,也有那么一点痕迹。
比如,一般情况下,我管外国人为老外,但是,对于黑人兄弟,我却常常会称之为黑人。
我可以用这个理由来辩解,在我们常见到的老外中,黑人兄弟比较少,所以称黑人,有更准确的指向性。而且,我确定,自己对于黑人没有任何偏见。
但是,如今仔细想来,黑人兄弟发现自己在中国,竟然连“老外”的称呼也不能享用,应该是有点不爽的。
所以,我今后,一定会注意对于非白人老外的称呼问题。
称呼事小,然种族观念问题事大。而且这种观念,就是在这么一点一滴中慢慢积累起来的。
原文链接:中国人的种族观念
也许是以偏概全,也有可能是旁观者清。
中国正在迅速地走向世界。之前,中国较少接触外面的世界,所以种族观念并未彰显。
而现在,与外界的联系日益加强,中国之国力也与日渐进,种族问题确实是值得国人注意。
就该文所讲的具体实例,在我自己的身上,也有那么一点痕迹。
比如,一般情况下,我管外国人为老外,但是,对于黑人兄弟,我却常常会称之为黑人。
我可以用这个理由来辩解,在我们常见到的老外中,黑人兄弟比较少,所以称黑人,有更准确的指向性。而且,我确定,自己对于黑人没有任何偏见。
但是,如今仔细想来,黑人兄弟发现自己在中国,竟然连“老外”的称呼也不能享用,应该是有点不爽的。
所以,我今后,一定会注意对于非白人老外的称呼问题。
称呼事小,然种族观念问题事大。而且这种观念,就是在这么一点一滴中慢慢积累起来的。
原文链接:中国人的种族观念
〖转贴〗1905:三种力量角力中国
【若昔难得 按:】一百年以前,“山雨欲来”。保皇派、立宪派、革命派,只是这百年风云的开始。之后的风起云涌,到有点不知如何说起的意味。
转念间就想到一句话,百年如一梦。
不过立刻就明白,所谓“梦”的说法,是不折不扣的bullshit。
凡走过必留下痕迹。
百年之后,再回头来看,2005又留下了什么痕迹。
原文链接:1905:三种力量角力中国
转念间就想到一句话,百年如一梦。
不过立刻就明白,所谓“梦”的说法,是不折不扣的bullshit。
凡走过必留下痕迹。
百年之后,再回头来看,2005又留下了什么痕迹。
原文链接:1905:三种力量角力中国
〖转贴〗让我们接近星星
【若昔难得 按:】先哲康德有句名言,头顶的星空与心底的良知。这话听起来很cool,有种让人感动的意思。
然而,我以外,越是让人感动的口号,我们越要加以提防。仔细分析,他究竟是在说什么,有何依据。理性是人类的最强武器,一定要善加利用。以上所说的,并非针对康德的这句名言。
在下对哲学家们先是敬而远之,后来又是敬谢不敏,所以对哲学家们实在是有点距离感。
单就康德而言,我倒是零星地读过他的一些言论。他的理性和反宗教的一面,很为我欣赏。大概算是哲学家中,最对我胃口的那一支了。
什么是头顶的星空呢?康德似乎是指无法穷究的自然运行规律。
是的,在人类有限的时间中,我们是无法穷尽这世界背后的规律的。然而,我们可以知道的,可以作到的,却远比大多数人心目中的那个界限更大。
实际上,这个世界上没有神,(相信神的诸位请原谅在下身为骄傲的无神论者的狂妄)更没有神定下的界限。
科学是没有边界的。
原文链接:让我们接近星星
然而,我以外,越是让人感动的口号,我们越要加以提防。仔细分析,他究竟是在说什么,有何依据。理性是人类的最强武器,一定要善加利用。以上所说的,并非针对康德的这句名言。
在下对哲学家们先是敬而远之,后来又是敬谢不敏,所以对哲学家们实在是有点距离感。
单就康德而言,我倒是零星地读过他的一些言论。他的理性和反宗教的一面,很为我欣赏。大概算是哲学家中,最对我胃口的那一支了。
什么是头顶的星空呢?康德似乎是指无法穷究的自然运行规律。
是的,在人类有限的时间中,我们是无法穷尽这世界背后的规律的。然而,我们可以知道的,可以作到的,却远比大多数人心目中的那个界限更大。
实际上,这个世界上没有神,(相信神的诸位请原谅在下身为骄傲的无神论者的狂妄)更没有神定下的界限。
科学是没有边界的。
原文链接:让我们接近星星
《ASD》设计模式:NULL Object
CFoo * pcf = bar.GetFoo();
if ( !pcf && cf.Ready() )
{
pcf->Execute();
}
这是一段非常常见的代码。但是有人觉得!cf的写法不够优雅,就说可以用NULL Object模式来简化这种写法。
NULL Object模式大致说来,就是从当前的CFoo类中,派生一个CNullFoo类出来,这个类什么事情也不做。比如说他的Execute函数可能就是一个空函数。
然后,当bar.GetFoo()失败了,原本要返回一个NULL的时候,就返回这个CNullFoo类的实例。
于是,在调用端,就不用做!pcf的判断了,不管GetFoo结果如何,都可以一致的对待。
这个方法虽然看起来不错,但是好像没有省多少事,却还增加了一个类,是否有点不值呢?
对于像我这样的C++程序员来说,可能不会马上想起nested class,但是《ASD》一书,是使用Java来写例子程序的。
所以,这个CNullFoo类,其实是CFoo的一个Nested Class。这样,这个方案就可以接受了。
但是在C++中,我还是觉得这个模式是没有什么价值的。
顺道一提,我对Java的看法。
我热爱C++,所以对于Java多少会有点欠缺热情。不过我希望我下面说的话尽量公正。
一般说来,C++有两大缺点,一个是和C语言兼容,一个是复杂性。
就和C语言兼容而言,确实不爽。然而,这更多是编程习惯的问题。大家都知道,那一部分是C++中不太光彩的部分,那么不要去用它不就好了吗。(我要故作天真状了。)
而对于复杂性,我个人觉得,还可以接受。相反倒是Java不够复杂,就丧失了很多非常cool的特性,而变得不够灵活。在复杂和受限之间,我宁愿选择复杂。
但是Java有两个特性,使我非常艳羡的。
一个是语言的动态性(反射机制),另一个是nested class。都是目前的C++在语言一级上,很难实现,又非常有用的东西。不过,好像在C++/CLI中,这两个特性都会出现。
^_^ 这太让人期待了。
if ( !pcf && cf.Ready() )
{
pcf->Execute();
}
这是一段非常常见的代码。但是有人觉得!cf的写法不够优雅,就说可以用NULL Object模式来简化这种写法。
NULL Object模式大致说来,就是从当前的CFoo类中,派生一个CNullFoo类出来,这个类什么事情也不做。比如说他的Execute函数可能就是一个空函数。
然后,当bar.GetFoo()失败了,原本要返回一个NULL的时候,就返回这个CNullFoo类的实例。
于是,在调用端,就不用做!pcf的判断了,不管GetFoo结果如何,都可以一致的对待。
这个方法虽然看起来不错,但是好像没有省多少事,却还增加了一个类,是否有点不值呢?
对于像我这样的C++程序员来说,可能不会马上想起nested class,但是《ASD》一书,是使用Java来写例子程序的。
所以,这个CNullFoo类,其实是CFoo的一个Nested Class。这样,这个方案就可以接受了。
但是在C++中,我还是觉得这个模式是没有什么价值的。
顺道一提,我对Java的看法。
我热爱C++,所以对于Java多少会有点欠缺热情。不过我希望我下面说的话尽量公正。
一般说来,C++有两大缺点,一个是和C语言兼容,一个是复杂性。
就和C语言兼容而言,确实不爽。然而,这更多是编程习惯的问题。大家都知道,那一部分是C++中不太光彩的部分,那么不要去用它不就好了吗。(我要故作天真状了。)
而对于复杂性,我个人觉得,还可以接受。相反倒是Java不够复杂,就丧失了很多非常cool的特性,而变得不够灵活。在复杂和受限之间,我宁愿选择复杂。
但是Java有两个特性,使我非常艳羡的。
一个是语言的动态性(反射机制),另一个是nested class。都是目前的C++在语言一级上,很难实现,又非常有用的东西。不过,好像在C++/CLI中,这两个特性都会出现。
^_^ 这太让人期待了。
《ASD》设计模式:Singleton和Monostate
Singleton几乎是最简单的模式了,我甚至认为,在一般情况下,他不是模式,而只是一个idiom。
但是,他确实功能强大,而且非常实用,又很常用。(所谓居家旅行杀人灭口必备之工具)
关于Singleton没有多少可以说的了。一句话的解释是,利用静态指针或引用来维护一个系统中某个类的唯一实例;利用静态方法来获取这个唯一实例;是全局变量的优雅替代。
《ASD》一书中,也没有对Singleton讲出多少新意来。
对于利用静态变量做出来的技巧,都可以考虑用模版类来自动化。Singleton就是一个非常好的例子。这个问题我会在以后关于《Modern C++ Design》的记录中来讨论。
Monostate模式却有些新意思,甚至有点搞笑的味道。
如果,以各类可以有多个实例,但是没个实例中的成员都一样,那又如何?那就是说,不论这个类有多少个实例,其实每个实例使用的内存都是同一段静态内存。每个实例都是同一个实例的不同化身。这也算是全局变量的优雅替代。
但是Monostate的实现要比Singleton来的麻烦,必须把每个变量都声明成静态变量。
那么这样做有什么好处呢?
《ASD》如是说:
透明性:使用者使用Monostate对象和使用普通对象没有什么区别。
可派生型:每个Monostate对象的派生类依然是Monostate。(我的理解是,这个特性的前提条件是,派生类不能添加非静态成员变量)
多态性:派生类中可以override父类的方法。
我认为透明性其实没有什么意思,而且有点危险。如果调用者不知道自己调用的是Monostate,而把它当作普通类来使用,那么Monostate的表现可够她大吃一惊的。
可是后两个特性却很有意思,非常有意思。
仔细考量Monostate的内存布局。所有的monostate派生类都共享同一块内存,但是派生类的函数内容却有所不同。也就是说,如果把所有的monostate类对象看作是同一个对象(从内存的角度来看,他们也的确就是一个对象),那么这个对象的函数会根据调用点的不同,而由不同的表现。
即,这个对象的功能可以响应当前的状态。这是一个非常优雅的状态机。
这就是Monostate模式的真面目,全局唯一的状态机。
如何实现Monostate模式的状态机,关键一点就是,在父类中,包含一个到子类的指针。父类的所有函数,都委托给该指针指向的子类对象。子类的函数,在状态切换时,把这个指针改变为指向下一个状态子类的实例。
但是,他确实功能强大,而且非常实用,又很常用。(所谓居家旅行杀人灭口必备之工具)
关于Singleton没有多少可以说的了。一句话的解释是,利用静态指针或引用来维护一个系统中某个类的唯一实例;利用静态方法来获取这个唯一实例;是全局变量的优雅替代。
《ASD》一书中,也没有对Singleton讲出多少新意来。
对于利用静态变量做出来的技巧,都可以考虑用模版类来自动化。Singleton就是一个非常好的例子。这个问题我会在以后关于《Modern C++ Design》的记录中来讨论。
Monostate模式却有些新意思,甚至有点搞笑的味道。
如果,以各类可以有多个实例,但是没个实例中的成员都一样,那又如何?那就是说,不论这个类有多少个实例,其实每个实例使用的内存都是同一段静态内存。每个实例都是同一个实例的不同化身。这也算是全局变量的优雅替代。
但是Monostate的实现要比Singleton来的麻烦,必须把每个变量都声明成静态变量。
那么这样做有什么好处呢?
《ASD》如是说:
透明性:使用者使用Monostate对象和使用普通对象没有什么区别。
可派生型:每个Monostate对象的派生类依然是Monostate。(我的理解是,这个特性的前提条件是,派生类不能添加非静态成员变量)
多态性:派生类中可以override父类的方法。
我认为透明性其实没有什么意思,而且有点危险。如果调用者不知道自己调用的是Monostate,而把它当作普通类来使用,那么Monostate的表现可够她大吃一惊的。
可是后两个特性却很有意思,非常有意思。
仔细考量Monostate的内存布局。所有的monostate派生类都共享同一块内存,但是派生类的函数内容却有所不同。也就是说,如果把所有的monostate类对象看作是同一个对象(从内存的角度来看,他们也的确就是一个对象),那么这个对象的函数会根据调用点的不同,而由不同的表现。
即,这个对象的功能可以响应当前的状态。这是一个非常优雅的状态机。
这就是Monostate模式的真面目,全局唯一的状态机。
如何实现Monostate模式的状态机,关键一点就是,在父类中,包含一个到子类的指针。父类的所有函数,都委托给该指针指向的子类对象。子类的函数,在状态切换时,把这个指针改变为指向下一个状态子类的实例。
《ASD》设计模式:Facade和Mediator
Facade模式和Mediator模式有些相似,虽然尚未相似到令人混淆的地步。在形式上他们是有明显区别的,在功效上却有共通之处,这样的相似更应该注意。
按照GOF的《Design Patterns》一书,Facade模式和Mediator模式都是用于连接多个类,简化类之间的联系。
Facade模式是把大系统藏在自己后面,由自己来做一个通用的接口。使用者不用关系下面的具体类,只要了解Facade的接口,就可以使用系统了。这几乎是最常用的设计模式了。
Mediator也是连接大系统,但是它的客户并不是系统的使用者,而是系统中的各个模块;相应的,它的目的只是为了降低系统中各个模块之间的依赖关系。而对于系统的使用者,Mediator常常是透明的。
《ASD》一书中,不仅强调了这两个模式的更多特性,还挖掘了这两个模式之间更内在的关系。即他们都是为了施加策略,Facade模式是从上面施加,Mediator是从下面施加。
所谓策略,即如何使用系统中的模块。
于此一说,如何应用Facade模式来施加策略就昭然若揭了。既然调用者只是发出命令,由Facade对象解释命并调用系统中的模块来执行命令,那么Facade对象就可以在执行时,控制各个模块之间的关系了。
这个有些Template模式或Strategy模式的味道。
由于是从调用这一端执行,所以可以说,这是从上面施加策略,或者可以说是一种主动策略。
在《Design Pattern》书中,Mediator对于系统的客户是透明的,但是系统中的各个模块基本上都还是指能够看到Mediator。
可是在《ASD》书中,Mediator对于系统中的各个模块基本上也是半透明的了。
Mediator知道所有的模块,和他们之间的关系。
而各个模块并不知道Mediator的存在,它们能看到的,只是一个消息系统,有事就发消息。
当然了,Mediator就在消息系统的另一端倾听。一旦收到了某个模块的消息,他就开始调用相关的其他对象,牵动整个系统。
策略就是如此在系统内部运转,这是这次除了Mediator自己以外,没有对象知道,策略已经开始运作了。
所以可以说,这是从下面施加策略,或者可以说是一种被动策略。
两者相比较,主动策略来的更直接,力量更强大。但是如果调用着绕过Facade的接口,直接与系统中子模块对话,策略就会无法实施。
被动策略是隐蔽的,是以相应的方式来运作的,在策略的实施上,有些限制。但是没有模块知道策略的实施,也就不能绕过这些策略。
按照GOF的《Design Patterns》一书,Facade模式和Mediator模式都是用于连接多个类,简化类之间的联系。
Facade模式是把大系统藏在自己后面,由自己来做一个通用的接口。使用者不用关系下面的具体类,只要了解Facade的接口,就可以使用系统了。这几乎是最常用的设计模式了。
Mediator也是连接大系统,但是它的客户并不是系统的使用者,而是系统中的各个模块;相应的,它的目的只是为了降低系统中各个模块之间的依赖关系。而对于系统的使用者,Mediator常常是透明的。
《ASD》一书中,不仅强调了这两个模式的更多特性,还挖掘了这两个模式之间更内在的关系。即他们都是为了施加策略,Facade模式是从上面施加,Mediator是从下面施加。
所谓策略,即如何使用系统中的模块。
于此一说,如何应用Facade模式来施加策略就昭然若揭了。既然调用者只是发出命令,由Facade对象解释命并调用系统中的模块来执行命令,那么Facade对象就可以在执行时,控制各个模块之间的关系了。
这个有些Template模式或Strategy模式的味道。
由于是从调用这一端执行,所以可以说,这是从上面施加策略,或者可以说是一种主动策略。
在《Design Pattern》书中,Mediator对于系统的客户是透明的,但是系统中的各个模块基本上都还是指能够看到Mediator。
可是在《ASD》书中,Mediator对于系统中的各个模块基本上也是半透明的了。
Mediator知道所有的模块,和他们之间的关系。
而各个模块并不知道Mediator的存在,它们能看到的,只是一个消息系统,有事就发消息。
当然了,Mediator就在消息系统的另一端倾听。一旦收到了某个模块的消息,他就开始调用相关的其他对象,牵动整个系统。
策略就是如此在系统内部运转,这是这次除了Mediator自己以外,没有对象知道,策略已经开始运作了。
所以可以说,这是从下面施加策略,或者可以说是一种被动策略。
两者相比较,主动策略来的更直接,力量更强大。但是如果调用着绕过Facade的接口,直接与系统中子模块对话,策略就会无法实施。
被动策略是隐蔽的,是以相应的方式来运作的,在策略的实施上,有些限制。但是没有模块知道策略的实施,也就不能绕过这些策略。
2005年2月17日星期四
《ASD》设计模式:Template和Strategy
Q:virtual函数可以是private函数吗?
A:可以。不仅可以,这还是Template模式中的常见方法。
Q:那么子类实现这些不能被调用的虚函数有什么意义呢?
A:意义在于这些虚函数是由一个public的非虚函数来调用的。
Template模式的核心是有一个相对固定的调用框架和可以被替换的具体步骤,父类实现这个调用框架,子类实现具体步骤。如果不想让客户随便的使用这些具体步骤,就可以把它们设为private函数,只能由父类的框架来调用。所以,对于一个类似于framework的产品,Template模式几乎是必不可少的。
Template模式是一种非常强大而且有趣的模式,但是它常常会被滥用。根本原因是:仔细考量Template模式的结构,会发现Template模式的实现完全是依靠继承。而GOF说过,要优先考虑聚合然后才是继承。
Template是直接继承,这使得具体实现的子类依赖于调用者父类,这不符合DIP原则(还能想起什么是DIP吗?)这就使得子类中的方法不能为其他对象所用。而常常,对于一个具体实现有不止一种策略,而每种策略都需要这个具体实现中的某个方法。这种时候,Template模式会变得很麻烦。
那么如何才能让它符合DIP原则呢?这就需要在调用者和实现者之间添加一个接口。这个接口声明所有的可能被调用的步骤;调用者包含这个接口,调用接口中的方法;实现者从这个接口中派生,实现接口中的所有步骤。
这个改动将大大地改善之前设计的灵活性。多种策略可以共享同一个实现了,当然一个策略也可以应用于多种实现上。策略和实现之前完全的松绑了。
实际上,这种设计是把之前的Templage模式便成了Strategy模式。
所以当,涉及到要把上层逻辑和下层实现分离的时候,应当优先考虑Strategy模式,而不是Template模式。
那么Template模式是否就没有用了呢?看到这种问句的人都该明白,答案一定是否定的。
刚才已经提到了,Template模式的问题之一是实现者的方法不能被其他对象使用。
而本文一开始就说了,Template模式可以把虚函数设成私有函数,以防止其他对象滥用具体实现类。也就是说,这是一个故意加上去的限制。
现在明白了吧,Template的不灵活原本就是他的意图,本意就是要给使用者添加一些限制。例如一个framework的产品,并不希望出现自己所不愿意见到的调用方法(那样也许会对产品本身造成破坏),所以就会使用Template模式。
结论是:Template模式在于限制,Strategy模式在于分离。
A:可以。不仅可以,这还是Template模式中的常见方法。
Q:那么子类实现这些不能被调用的虚函数有什么意义呢?
A:意义在于这些虚函数是由一个public的非虚函数来调用的。
Template模式的核心是有一个相对固定的调用框架和可以被替换的具体步骤,父类实现这个调用框架,子类实现具体步骤。如果不想让客户随便的使用这些具体步骤,就可以把它们设为private函数,只能由父类的框架来调用。所以,对于一个类似于framework的产品,Template模式几乎是必不可少的。
Template模式是一种非常强大而且有趣的模式,但是它常常会被滥用。根本原因是:仔细考量Template模式的结构,会发现Template模式的实现完全是依靠继承。而GOF说过,要优先考虑聚合然后才是继承。
Template是直接继承,这使得具体实现的子类依赖于调用者父类,这不符合DIP原则(还能想起什么是DIP吗?)这就使得子类中的方法不能为其他对象所用。而常常,对于一个具体实现有不止一种策略,而每种策略都需要这个具体实现中的某个方法。这种时候,Template模式会变得很麻烦。
那么如何才能让它符合DIP原则呢?这就需要在调用者和实现者之间添加一个接口。这个接口声明所有的可能被调用的步骤;调用者包含这个接口,调用接口中的方法;实现者从这个接口中派生,实现接口中的所有步骤。
这个改动将大大地改善之前设计的灵活性。多种策略可以共享同一个实现了,当然一个策略也可以应用于多种实现上。策略和实现之前完全的松绑了。
实际上,这种设计是把之前的Templage模式便成了Strategy模式。
所以当,涉及到要把上层逻辑和下层实现分离的时候,应当优先考虑Strategy模式,而不是Template模式。
那么Template模式是否就没有用了呢?看到这种问句的人都该明白,答案一定是否定的。
刚才已经提到了,Template模式的问题之一是实现者的方法不能被其他对象使用。
而本文一开始就说了,Template模式可以把虚函数设成私有函数,以防止其他对象滥用具体实现类。也就是说,这是一个故意加上去的限制。
现在明白了吧,Template的不灵活原本就是他的意图,本意就是要给使用者添加一些限制。例如一个framework的产品,并不希望出现自己所不愿意见到的调用方法(那样也许会对产品本身造成破坏),所以就会使用Template模式。
结论是:Template模式在于限制,Strategy模式在于分离。
2005年2月16日星期三
《ASD》设计模式:Command和Active Object
Command模式:
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. - 《Design Patterns》

Command模式的关键在于只包含一个 Execute方法,子类在实现这个接口时,在Execute方法中,完成特定的任务。可以说,这是一个非常简单的模式。
《ASD》中提到了该模式的三种用法:
1. Invoker可以和任意一个Command挂钩,而且不需要了解这到底是个什么ConcreteCommand,然后在需要的时候调用这个Command对象的Execute方法就行了。这在消息驱动的的系统中非常常见,每个trigger就是一个invoker。那么如何把Command和invoker挂钩呢?方法很多,最cool的方法是在系统外用一个配置文件来指定。这样不需要重新编译就可以改变软件运行的方式。可以参考Source Insight的界面。Source Insight中可以任意配置菜单项和工具栏按钮。其实现应该就是应用了这种Command模式。
2.上面的方法是否让人想起了Template模式?有点相像吧。顺着这个思路去想,就可以把Command模式应用于Transaction。让一个类来解决Transaction的init和uninit问题,中间包含一个Command的队列。这样就可以把这个队列中的全部command当作一个transaction了。这样的作法可以把Transaction的实现和逻辑分离开来,是很漂亮的实现。同样的思路,也可以用在类似的问题上,需要init和uninit,中间有不定量的操作。
3.如果真的用来解决transaction问题,那么就必须具备roll back的能力。然而这个很容易实现,只要在command类中,添加undo方法就可以了。剩下的活交给invoker来处理。
4.此外还有一个附带的好处。command类和一个单独的execute方法其实很相似,但是command类的对象有生命周期,可以由程序来控制。因此,一个command对象,可以在提交了很长时间以后再批量执行。
除了这些以外,《Design Pattern》还提到了Command模式的其他使用方法。虽然这些方法未必使用,但我还是把它们列在这里:
1.command对象和command对象的序列都可以serialization。这样如果软件被有意或无意的中止(例如crash),在重新启动后,还可以接续之前没有完成的任务。
2.Command模式如果和Composite模式接合,就可以作出MacroCommand。^_^,这个idea虽然很cool,但是可以用到的地方大概不多吧。
Active Object模式不属于《Design Pattern》23模式。实际上,她是一种特殊的Command Queue。其特殊之处在于:
1. 队列的拥有者会顺序地执行队列中所有Command对象的Execute方法。(这个其实不算特殊)
2.Command对象在自己的Execute方法结束前,可以把一个新的command对象(实际上常常是这个command对象自己)再加到队列的尾部。
看出来了吗,这个队列有可能不会终止的,他可以一直执行下去。这个可以作为一个应用或者服务的主模块了,想像一下她可以作多少事情吧。
《ASD》指出这个模式可以用来在一个线程中处理多任务的问题!!! ^_^ 太cool了。
如何处理呢?你可以把每个command对象看作是一个任务。他在Execute函数中,处理自己的任务,在任务告一段落时,记录自己的状态,然后把自己插入到队列的尾部,结束Execute方法。当队列轮完一周后,又会再次执行这个command对象的Execute方法。 ^_^ 很cool吧。
那么这种方法和多线程的方法相比有什么有缺点呢?
最大的优点是,所有的command都在同一个线程中,因此切换时,不需要进入内核模式!!超高效啊!!而且,可以有很多很多的command,数量上远远超过多线程的数量。
缺点嘛,是这种方法需要用户自己来实现调度,另外这其实是一种非剥夺模式的多任务,如果command处理不好,就会连累其它所有的command,因此实际上比多线程要更复杂。(嘿嘿,程序员能够怕麻烦吗?)
还有一点,Active Object运行于单线程,也就是说,她不能享受多处理器或多处理器核心带来的性能上的改善。
其实,这最后一点是非常致命的一点。也就是说,在当前intel的超线程CPU机器上,如果系统的负担并不重的时候。Active Object的效率有可能比多线程更低。
Anyway,这是一个非常有趣的模式。只是一般的程序员可能没有机会用到。但是请记住她,也许能有那么一次机会,可一用她来爽上一把。
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. - 《Design Patterns》
Command模式的关键在于只包含一个 Execute方法,子类在实现这个接口时,在Execute方法中,完成特定的任务。可以说,这是一个非常简单的模式。
《ASD》中提到了该模式的三种用法:
1. Invoker可以和任意一个Command挂钩,而且不需要了解这到底是个什么ConcreteCommand,然后在需要的时候调用这个Command对象的Execute方法就行了。这在消息驱动的的系统中非常常见,每个trigger就是一个invoker。那么如何把Command和invoker挂钩呢?方法很多,最cool的方法是在系统外用一个配置文件来指定。这样不需要重新编译就可以改变软件运行的方式。可以参考Source Insight的界面。Source Insight中可以任意配置菜单项和工具栏按钮。其实现应该就是应用了这种Command模式。
2.上面的方法是否让人想起了Template模式?有点相像吧。顺着这个思路去想,就可以把Command模式应用于Transaction。让一个类来解决Transaction的init和uninit问题,中间包含一个Command的队列。这样就可以把这个队列中的全部command当作一个transaction了。这样的作法可以把Transaction的实现和逻辑分离开来,是很漂亮的实现。同样的思路,也可以用在类似的问题上,需要init和uninit,中间有不定量的操作。
3.如果真的用来解决transaction问题,那么就必须具备roll back的能力。然而这个很容易实现,只要在command类中,添加undo方法就可以了。剩下的活交给invoker来处理。
4.此外还有一个附带的好处。command类和一个单独的execute方法其实很相似,但是command类的对象有生命周期,可以由程序来控制。因此,一个command对象,可以在提交了很长时间以后再批量执行。
除了这些以外,《Design Pattern》还提到了Command模式的其他使用方法。虽然这些方法未必使用,但我还是把它们列在这里:
1.command对象和command对象的序列都可以serialization。这样如果软件被有意或无意的中止(例如crash),在重新启动后,还可以接续之前没有完成的任务。
2.Command模式如果和Composite模式接合,就可以作出MacroCommand。^_^,这个idea虽然很cool,但是可以用到的地方大概不多吧。
Active Object模式不属于《Design Pattern》23模式。实际上,她是一种特殊的Command Queue。其特殊之处在于:
1. 队列的拥有者会顺序地执行队列中所有Command对象的Execute方法。(这个其实不算特殊)
2.Command对象在自己的Execute方法结束前,可以把一个新的command对象(实际上常常是这个command对象自己)再加到队列的尾部。
看出来了吗,这个队列有可能不会终止的,他可以一直执行下去。这个可以作为一个应用或者服务的主模块了,想像一下她可以作多少事情吧。
《ASD》指出这个模式可以用来在一个线程中处理多任务的问题!!! ^_^ 太cool了。
如何处理呢?你可以把每个command对象看作是一个任务。他在Execute函数中,处理自己的任务,在任务告一段落时,记录自己的状态,然后把自己插入到队列的尾部,结束Execute方法。当队列轮完一周后,又会再次执行这个command对象的Execute方法。 ^_^ 很cool吧。
那么这种方法和多线程的方法相比有什么有缺点呢?
最大的优点是,所有的command都在同一个线程中,因此切换时,不需要进入内核模式!!超高效啊!!而且,可以有很多很多的command,数量上远远超过多线程的数量。
缺点嘛,是这种方法需要用户自己来实现调度,另外这其实是一种非剥夺模式的多任务,如果command处理不好,就会连累其它所有的command,因此实际上比多线程要更复杂。(嘿嘿,程序员能够怕麻烦吗?)
还有一点,Active Object运行于单线程,也就是说,她不能享受多处理器或多处理器核心带来的性能上的改善。
其实,这最后一点是非常致命的一点。也就是说,在当前intel的超线程CPU机器上,如果系统的负担并不重的时候。Active Object的效率有可能比多线程更低。
Anyway,这是一个非常有趣的模式。只是一般的程序员可能没有机会用到。但是请记住她,也许能有那么一次机会,可一用她来爽上一把。
异形中的两只狗
去年年底的时候,复习了一遍异形四部曲,看到一个很有趣的细节。在第一集中,Ripley养有一只小狗。很乖巧的小猎犬,就是Snoopy那种。
电影的后半段异形在飞船中,横冲直撞,大肆屠杀。可这只狗却安然无恙,虽然她也曾经被异形追踪过。在异形四部曲中,能够从异形口中逃生的,除了女主角Ripley以外,就是这只狗了。最后,飞船上的船员,除了Ripley之外,全都被杀死。只有Ripley和小狗乘坐救生船逃生了。
是小狗的求生能力很强吗?当然不是,只是导演很强而已。在好莱坞的电影中,坏人可以死,好人可以死,连世界毁灭也不算什么,可是杀死一只无辜的小狗,那可就真是大事件了。
所以,第二集中,Ripley一醒来,首先就要去找那只小狗,小狗没事就好。至于Ripley在太空中漂流了70多年,连女儿都已经死了。观众于此,虽然有些感同身受的伤感,却也还能够接受。
之后,Ripley第二次出发去和Aliens干架。这次如果再让异形们把人类杀个干净,偏偏留下小狗作活口,剧情上就太不通顺。可是又万万不能杀小狗,怎么办呢?导演这次把小狗留在家中了。让女英雄Ripley一个人去拼命吧。
到了异形第三集的时候,导演换成了新锐大卫芬奇(David Fincher)。这哥们尽管现在很牛,可在异形第三集的时候,即没有拍Se7en,也没有拍Fight Club,仍然是个毛头小伙子。所以他一心要把异形第三集拍成一个反传统的电影。其决心之大,在于电影故事还没有开始,就把第二集中和Ripley有点感情戏的大兵和Ripley拼死救出的小女孩给杀了。我敢肯定,如果第二集中,Ripley把那只小狗带在身边,那么那只小狗也必定会被大卫给杀掉。
可是没有杀狗,多少让大卫有点不过瘾。所以大卫安排异形首先寄生在监狱里的一只狗身上,然后破狗而出,再大肆咬人。对观众来说,杀狗可比杀人要震撼多了。所以,荧幕上,异形可以把人撕碎,但是绝不能正面出现异形杀狗的场面。
后来,异形第三集的DVD中,有一个加长版,并不是由大卫剪辑的。在这个版本中,异形寄生的是一只牛,而不是狗。没有杀狗的情节了。
我看DVD的时候,首先看的是加长版。看到牛被杀死的时候,确定这个加长版是个垃圾,然后就切换回原版去看了。
提到电影中被杀死的动物,让我想起,在教父第一集中,有个很出名的情节。
教父为了威胁电影制片人,把制片人的马杀死了,再把马头藏在制片人的被子下。制片人醒来时,被吓得半死,然后就乖乖听话了。
科波拉在教父DVD的评论音轨中提到,电影上映后,有很多人打电话或写信来批判为了拍电影就杀死一匹美丽而无辜的马。
科波拉解释他们并没有杀马。他们本来打算用一个假马头来拍这段戏,可是效果不好。后来他们发现有宠物食品公司用马肉来作宠物食品罐头,于是他们就找到宠物食品公司,选了匹漂亮的马,买下了马头,用来拍了那段戏。当然,马的身体还是被用来作成了罐头喂给了猫和狗。
电影的后半段异形在飞船中,横冲直撞,大肆屠杀。可这只狗却安然无恙,虽然她也曾经被异形追踪过。在异形四部曲中,能够从异形口中逃生的,除了女主角Ripley以外,就是这只狗了。最后,飞船上的船员,除了Ripley之外,全都被杀死。只有Ripley和小狗乘坐救生船逃生了。
是小狗的求生能力很强吗?当然不是,只是导演很强而已。在好莱坞的电影中,坏人可以死,好人可以死,连世界毁灭也不算什么,可是杀死一只无辜的小狗,那可就真是大事件了。
所以,第二集中,Ripley一醒来,首先就要去找那只小狗,小狗没事就好。至于Ripley在太空中漂流了70多年,连女儿都已经死了。观众于此,虽然有些感同身受的伤感,却也还能够接受。
之后,Ripley第二次出发去和Aliens干架。这次如果再让异形们把人类杀个干净,偏偏留下小狗作活口,剧情上就太不通顺。可是又万万不能杀小狗,怎么办呢?导演这次把小狗留在家中了。让女英雄Ripley一个人去拼命吧。
到了异形第三集的时候,导演换成了新锐大卫芬奇(David Fincher)。这哥们尽管现在很牛,可在异形第三集的时候,即没有拍Se7en,也没有拍Fight Club,仍然是个毛头小伙子。所以他一心要把异形第三集拍成一个反传统的电影。其决心之大,在于电影故事还没有开始,就把第二集中和Ripley有点感情戏的大兵和Ripley拼死救出的小女孩给杀了。我敢肯定,如果第二集中,Ripley把那只小狗带在身边,那么那只小狗也必定会被大卫给杀掉。
可是没有杀狗,多少让大卫有点不过瘾。所以大卫安排异形首先寄生在监狱里的一只狗身上,然后破狗而出,再大肆咬人。对观众来说,杀狗可比杀人要震撼多了。所以,荧幕上,异形可以把人撕碎,但是绝不能正面出现异形杀狗的场面。
后来,异形第三集的DVD中,有一个加长版,并不是由大卫剪辑的。在这个版本中,异形寄生的是一只牛,而不是狗。没有杀狗的情节了。
我看DVD的时候,首先看的是加长版。看到牛被杀死的时候,确定这个加长版是个垃圾,然后就切换回原版去看了。
提到电影中被杀死的动物,让我想起,在教父第一集中,有个很出名的情节。
教父为了威胁电影制片人,把制片人的马杀死了,再把马头藏在制片人的被子下。制片人醒来时,被吓得半死,然后就乖乖听话了。
科波拉在教父DVD的评论音轨中提到,电影上映后,有很多人打电话或写信来批判为了拍电影就杀死一匹美丽而无辜的马。
科波拉解释他们并没有杀马。他们本来打算用一个假马头来拍这段戏,可是效果不好。后来他们发现有宠物食品公司用马肉来作宠物食品罐头,于是他们就找到宠物食品公司,选了匹漂亮的马,买下了马头,用来拍了那段戏。当然,马的身体还是被用来作成了罐头喂给了猫和狗。
订阅:
博文 (Atom)