作者:空明流转 来源:C++博客   酷勤网收集 2007-11-13

摘要
  几乎没什么程序设计经验的新手,重点读第一章,第二章,明白一些大概的原则;有一定的程序设计经验的人,读自己熟悉的模式,以及一些简单的、易于理解的模式,注意区别自己以前在遇到类似问题的时候是怎么解决的,GOF是怎么解决的,为什么…

本文将简单的介绍一下我读《设计模式》(GOF95)这本书的经验和教训。

我想首先应该讲的是,这本书应该怎么读。

几乎没什么程序设计经验的新手,重点读第一章,第二章,明白一些大概的原则;
一定的程序设计经验的人,读自己熟悉的模式,以及一些简单的、易于理解的模式,注意区别自己以前在遇到类似问题的时候是怎么解决的,GOF是怎么解决的,为什么;不需要太过于区分一些接近的模式,顺其自然就好。
对于如果你能熟练的编码,并开始拥有自己的设计思路的人,重点在学习用GOF的方式描述自己的经验,寻找自身思路和GOF的思路的差异性,熟悉一些自己没太用过的模式。
如果是已经阅读过设计模式,并且已经有了一些运用经验的人,详细读第一章,第二章,以及每一大类的模式的总结和每个模式的最后一节,注意区分模式之间的差异性以便于在系统中准确的选择。

接下来我希望能和大家讨论一下我对于设计模式的核心动机的理解,欢迎大家回帖的时候各抒己见,我会将大家的意见整理出来,并放在帖子中~

首先讨论一下构造型模式

Factory Method
描述
是最基本的构造型模式。它将构造从单纯的构造函数中分离出来。
动机
用于封装复杂的或者是易于变化的构造操作。
适用性/作用
适用于创建那些构造时需要初始化的对象,或者需要按一定次序构造对象的时候。该模式结构很简单,额外的代码量也很少,因此适合在开发的开始阶段就可以使用,它通常不会有那些“过度设计”的麻烦。它的另外一个实际作用,是可以统一程序的对象构造风格,增强可读性。

Abstract Factory
适用性/作用:
维护一个对象系列的一致性。当存在多个并行继承体系,并且每一组继承都较为独立,且容易错误的混用时候,建议使用该模式来构造对象系列。

Builder
适用性/作用:
当对象构造步骤复杂,并且每个步骤都使用同样的接口,但是它们具备不同实现的时候,可以选择该模式。常用于创建 构造逻辑接近、但其构造实现差异很大 的一系列对象,如构造不同格式的文本文档(GOF的示例),它们的构造逻辑接近,但是实现却是与平台相关的。

Prototype
适用性/作用:
这个模式的本意是从一个对象构造另外一个对象。在一个应用系统中,由于我们通常也能很方面的使用其它的构造方法;或者我们只需要一个按照默认初始化方式构造的对象,这样并不适合使用原型。当我们希望从不是由客户代码控制的对象中复制出一个相同的但是被客户代码管理的对象(管理权移交);或者是对象需要深拷贝语义;或者是两个子系统协作时,一个系统无法方便的访问到另外一个系统的构造函数,但是确能很方面的获得它的实例时(往往是由参数传递进来的),便需要使用这样一个模式存在。

Singleton
适用性/作用:
提供一个全局唯一的描述。还可以用来取代一些难以避免的全局变量。

有关于构造型的一些个人看法:
在C++中,由于构造函数不能多态调用虚成员函数,因此很多时候Factory Method成为了维持构造-初始化的一致性的逼不得已的选择;但是在C#一类的语言中,由于构造函数本身是虚函数,因此一些Factory Method的作用可以通过将Template Method运用到构造函数中来避免一部分Factory Method的使用;Factory Method更加强调于封装可能的构造方法变化,以及统一设计风格,而不是隐藏不必要的构造步骤。

然后是结构型模式

Adapter
描述:
转换接口的格式(也可以说转换接口的signature)。
适用性/作用:
通常是为了统一程序风格。例如在开发系统时,发现某一个系统所使用的程序库接口不符合系统约定,便需要使用Adapter模式适配接口,使风格统一。该模式也可用于使两个系统接合到一起工作。

Bridge
描述:
分离接口与实现。
适用性/作用:
Bridge的Interface与其Implementation通常在程序的架构中是垂直关系,即上层调用下层。在基础设置,即Impl发生变化的时候,可以避免使用Interface的客户代码发生变化。将实现部分的变化消灭在接口上。
在一些使用beta版的lib开发软件,或者是开发所依赖的库有较大的可能性在软件的生命期内发生改变时,通常回使用该模式。

Composite
适用性/作用:
描述嵌套的结构,类似于程序设计里的递归,当每一个对象既是一个有独立功能的对象,也是一个容器时,通常会选择该模式,典型的例子就是XML Document。每一个节点都可能包含了叶节点。
需要注意的是,这些被包容和对象,和容器都具有及其类似的逻辑;如果从接口的层面上,可以认为这些对象是自包含的。

Decorator
描述:
动态、透明的为对象添加职责。
适用性/作用:
该模式有两个主要参与者,Decoratee和Decorator。前者为原有的对象,而后者则是在前者的基础上添加了新的功能。在GOF的描述中,Decorator和Decoratee使用了相同的接口。同时大部分的相关材料都侧重于这一模式“添加职责”的功能。但是有一点个人认为,在该模式的使用中还有两点值得考虑
首先,Decorator和Decoratee不仅在继承体系中属于同一层,在整个系统的功能体系中也应当是平级的。Decoratee自身也需要有相当的可能性被独立的使用到,否则在两个对象之间建立Decorator-Decoratee的关系就不那么必要,也许将Decoratee在功能体系中下移是个更好的选择,因为它会避开“相同接口”这样一个限制;
其次,Decorator之间的功能划分最好是正交的。一旦Decorator之间出现了不一致性,那么Decorator的装饰次序将会影响到实际的结果,或者对某个Decorator而言Decoratee存在着限制。而这些运行时才有的特性很难在静态设计中体现出来,会造成对系统功能的理解造成障碍。

Facade
描述:
为子系统中的一组接口提供一个统一的界面
适用性/作用:
由于子系统间的类联系比较紧密,他们通常会一一种固定的协作方式完成某项任务。当客户代码多以这种粗粒度的方式与子系统打交道时,Facade模式便有必要起来。
Facade模式可以用来解决以下两个问题
第一,如果某种粗粒度任务都是由一系列的细粒度组件以一定的语义关系耦合起来。而客户代码只关心这种粗粒度的任务,但不关心这个任务究竟怎样由子系统的组件合作完成的。那么这是,可以使用Facade模式将细粒度之间的语义耦合封装起来,并且这种封装可以防止子系统组件间合作的改变对客户代码造成影响。
第二,也就是GOF所描述的,简化客户使用子系统的复杂度。
Facade本身的设计在实践过程中会遇到以下几种情况
第一种情况下,Facade本身具有很高的内聚,同时它完全封装了子系统(说明子系统内聚度高),客户代码不需要知道子系统的细节便可以用Facade完成全部的工作。这一点是最理想的情况。
更多的可能是,Facade不能包办一切,程序既要与子系统打交道,也要与Facade打交道。这种情况下,如果Facade使用不当,便会破坏系统的层次结构。
个人的经验是,
如果一些子任务可以由Facade封装而完全不需要与子系统打交道,便对子系统的部分粗粒度任务使用Facade模式,保持局部的内聚性和抽象能力;
如果一个功能,客户通常都直接调用Facade,但是在少数情况下不可避免的要与细粒度组件打交道,那么通常我会将Facade旁置为一些Help Function,让它们与子系统组件处于差不多同样的地位,并以文档显示地注明其实现原理。
当然,能通过修改子系统的设计避免这种情况的出现是最完美的结局了。

Flyweight
适用性/作用:
呃,关于这个我想GOF已经描述的非常好了,我也就不说废话了。注意Flyweight在其它模式中的应用就行了。我经常就会在该用它的时候忘了用了。Flyweights通常由pool手法实现。

Proxy
适用性/作用:
Proxy的目的很明确,在维持原有接口和功能设计情况下,解除原始对象的限制。这最后的一句话是我的理解。通常一个对象实现一个功能直接实现就可以了;但是在一些应用中,会有一些与功能无关的限制和要求,例如对象可能是远程的,或者有内存方面的限制等等。为了解除这些限制,需要一些额外的手法来达到这样的目的。但是它们所附加的内容在功能设计时并没有描述。也就是说,这些不是客户需要关心的,它们应该被抽象掉。Proxy就是为了隐藏这些与代码的核心功能无关的实现,让客户端认为它是个没有限制的对象。

对象型模式讲完了,下面的行为型模式我大概分两次讲,欢迎大家的板砖。

来自:http://www.cppblog.com/lingjingqiu/archive/2007/11/02/35750.html 和 http://www.cppblog.com/lingjingqiu/archive/2007/11/06/35969.html

分类: 设计模式 系统架构



关于酷勤 | 联系方式 | 免责声明 | 友情链接