代码改变世界

【读书笔记】设计模式沉思录

2010-06-02 23:49  横刀天笑  阅读(3642)  评论(6编辑  收藏  举报

当我在china-pub上看到这本书的预售消息后就下订单了,不知道多少日子之后收到了china-pub的邮件。不过书到手后我好失望,怎么才140页~~~忽悠谁呢。

抱着浓缩的都是精华的心态,我看下去了,今儿终于给看完了,趁着还有几分热度赶快给记录下来。

这本书的内容主要来自John Vlissides(Gof之一)为C++ Report写的一些小文,然后经过整理调整组织成本书。可惜的是John英年早逝(于2005年11月24日因脑瘤病故,享年44岁),不然我想留下的佳作也许更多。

书是葛子昂翻译的,质量上乘,从中文来看本书原文肯定就很有趣,很难翻译,可翻译后的中文却如此流畅,佩服佩服。

书的第一章就来个下马威:对模式的十大误解。第一误解就直指亚历山大对模式的定义:模式就是在一种场合下对某个问题的一个解决方案。作者还举了个非常诙谐的例子来证明这种误解。

后面几条大部分在阐述这么一个道理:模式不是圣经、模式不是万能钥匙、模式不是救世主、模式也不限于面向对象领域。

篇幅最多的当属本书第二章,运用模式进行设计。作者用大家都熟悉的计算机的层次文件系统作为例子,用模式来为应用开发人员设计一个编程模型(API)。

首先要建模出这种层次文件系统的结构,当属组合模式(Composite)莫属:文件夹(Composite)、文件(Leaf)。

image

然后新的需求来了,我们要在这种文件系统中添加对快捷方式的支持,快捷方式实际上就是指向实际文件或者文件夹的一个链接,当然,我们可以将其也作为一个Leaf处理,不过和真实的文件不同,比如删除快捷方式不会删除对应的文件。如是这里引入了代理模式(Proxy)。

随着文件系统越来越完善,提供的功能也势必增多,这个时候就要给这个层次结构中的一些类添加各种接口,这个结构势必就变得越来越臃肿。那如何在不修改层次结构中的类就可以添加新的操作呢?这个时候引入了访问者模式(Visitor)。注意这里Visitor模式的用法,提供多个visit重载,将运行时绑定提前到编译时绑定,并且提供一个通用的visit,可以处理所有为预设的操作。

不过使用Visitor的时候要注意的是,你的这个结构必须稳定,比如我们这里就只有文件夹、文件、快捷方式三种,在可预见的未来是比较稳定的,如果我们要往这其中添加一个新的该怎么办?那Visitor就有点尴尬了。

然后在为了对文件系统进行保护的时候,由于有些方法,比如对文件系统read时,安全授权是通用的,但是对文件夹、文件、快捷方式的实现各不相同,在这里引入了模板方法设计模式(Template Method)。模板方法设计模式是进行框架程序设计时最常用的模式,它可以实现反向的控制,比如我们的ASP.NET中,页面的处理流程是固定的,但是每个页面的实现却不相同,通过模板方法设计模式,将相同的部分放到Page类里,然后不同的部分定义为虚方法,提供给子类(也就是我们具体的页面)实现,比如OnLoad方法。

现代的文件系统基本上都是基于多用户的,这就需要对登录用户的访问,来看看该用户是否对该文件或文件夹有访问的权限,在这里引入了单件模式(Singleton)。这里还提供了如何设计允许有多个实例的Singleton的方案。

有了用户,当然就有用户组的概念了,而且常识告诉我们,一个用户可以属于多个分组,一个分组里也有多个用户,这就是一种多对多的关系,如果直接把这种关系建立在User和Group对象本身,势必带来不必要的麻烦。在这里作者又引入了中介者模式(Mediator)。

就这样这个文件系统的编程模型终于设计完毕了,总共涉及了Composite、Proxy、Visitor、Template Method、Singleton、Mediator等六个模式。而且在设计当中,作者还用实例告诉我们如何从模式列表中选择一个合适的模式。

第三章标题为主体和变体,不过还没有理解这个名字的含义。。。。

开篇作者就问,Singleton这个模式为啥从来都没有涉及删除的问题呢?谁来删除Singleton?是有意的回避么?最后作者给出了解决方案,不过方案看看就可以了,对.Net等有GC的平台,没有明确销毁的析构函数,作者的解决方案基本上无效。

实际上在.Net里,Singleton也基本上是不可能删除的,使用了类成员,除非你unload应用程序域。作者的方案实际上后来在C++的标准库里出现了,就是所谓的auto_ptr。

后来作者在这里公开了一个未出现在《设计模式》这本书里的一个模式:Generation Gap模式,作者的说法是当时没有找到该模式的应用实例,选到《设计模式》这本书里的所有模式,必须要在现实中找到两个实例才行。

这个模式是这样的,当我们用设计工具设计界面,然后工具自动生成界面的代码,但是我们又要给这些界面编写一些行为,那如果后来要修改界面的时候,工具就会重新生成那些代码,如果我们自己编写的行为代码和界面代码放到一起,那我们写的代码势必会被工具给覆盖了,如是就提出了这么个解决方案:image

CoreClass就是工具生成的描绘界面的代码,ExtensionClass是我们自己编写的行为的代码。看到这个东东你是不是想到WinForm里的Form.designer.cs和Form.cs?.Net里用分部类完美的解决了这个问题,Form.designer.cs里放的是工具(Visual Studio)生成的界面代码,Form.cs是我们写的逻辑代码。只是这里结构变了,在编译时Form.designer.cs和Form.cs会合并成一个类,而没有了继承关系。

Type Laundering

Laundering,有淡化洗涤的意思。说的是这样的:

public void  Do(Node n)

{

 

}

Do方法接收的是一个基类,该基类下还有很多具体类。但是在这个传递过程中,这些具体类的信息都丢失了,在Do方法里为了访问Node某个具体类的特有的功能,必须向下转型,我们都知道向下转型是个非常恶心而且非常不安全的做法,在编译时根本发现不了,或许就到运行时来个崩溃。

这本来是一个缺点,但是作者话锋一转,却在另一个场景里利用这个缺点给出了一个非常完美的方案。

大家都知道,在迭代器模式里,我们有这么两个参与者:Subject(被迭代的对象),Cursor(一个游标)。不同的Subject,比如Hashtable、List等都有对应的Cursor,而且这些Cursor只能被对应的Subject访问,比如你就在外面的代码中实例化一个Cursor,然后访问里面的东东,这没有任何意义嘛,脱离了容器的存在,你的游标游到哪里呢?

为了只能让对应的Subject访问到对应的Cursor,如是我们把Cursor里的信息都隐藏起来(private),但是这样一来Subject如何访问了呢?好办,将Subject作为Cursor的友元类(.Net里不能添加友元类,但可以有友元程序集,关于C++的友元,可以这么理解,比如A是B的友元,那么A就可以访问B的私有成员)。C++里用friend关键字来声明友元,但是有个问题是friend不能继承,也就是在父类里声明了友元,子类里并不能继承到,这就麻烦了。

这里我们就遇到了一个矛盾,为了防止Cursor的信息泄露,被“无知”的客户代码滥用,我们将Cursor的信息private起来,但是又必须让Subject可以使用,如是引入了friend,但是friend又不能被继承。这可咋办呢?

如是这里引入了一个抽象的基类Cursor,抽象的基类是不可以实例化的啊,而且这个抽象基类只有一个公有的析构函数,构造函数是私有的,其余啥也没有,然后我们的具体的Cursor,比如ListCursor就从Cursor派生,但是我们要使用到ListCursor的功能,就必须先将Cursor向下转型为ListCursor,然后可以使用,假设我们这里的容器类是List,List肯定是“知道know”ListCursor的,而我们的客户代码,却不知道ListCursor,就无法做这个转型,所以就滥用不了这里的游标了。

其实这里的做法就是利用继承作为一种封装手段,屏蔽掉子类的信息。回忆我们大部分对继承、接口等的应用,实际上大部分时候我们都不是要实现复用的目标。这里的例子也很好的体现了这一点。

不过值得一提的是,在C#中,我们有了嵌套类,我们可以把这个游标实现容器类的内部,又将信息隐藏推进了一步。

第四章叫爱的奉献,不知道为啥叫这么个名字,呵呵。

讨论的是Multicast这又一没有出现在《设计模式》这本书中的模式,在这一章中精彩的是Gof四个人关于这个模式的一些争论。Multicast是观察者模式的兄弟,有几分相似,但又有一些微妙的不同,要问什么是Multicast模式?实际上.Net现在使用的事件机制就是这种模式~~~所以也就不在这里罗嗦了~~

 

书很不错,就是篇幅太短了,看得不过瘾。还有一点是,如果你要看这本书,必须先对《设计模式》这本书有个了解,书中很多讨论会引用《设计模式》这本书里的一些东西。突然想起来有人在china-pub上的留言:大师已逝,墨迹留香。