结构型模式依赖于同一个很小的语言机制集合构造代码和对象:单继承和多重继承机制用于基于类的模式,而对象组合机制用于对象式模式。但是这些相似性掩盖了这些模式的不同意图。
适配器 与 桥接
具有一些共同的特征。它们都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。它们都涉及到从自身以外的一个接口向这个对象转发请求。
这些模式的不同之处主要在于它们各自的用途。
适配器模式主要是为了解决两个已有接口之间不匹配的问题。它不考虑这些接口是怎样实现的,也不考虑它们各自可能会如何演化。 这种方式不需要对两个独立设计的类中的任一个进行重新设计,就能够使它们协同工作。
另一方面, 桥接模式则对抽象接口与它的(可能是多个)实现部分进行桥接。虽然这一模式允许你修改实现它的类,它仍然为用户提供了一个稳定的接口。 桥接模式也会在系统演化时适应新的实现。
由于这些不同点,适配器和桥接模式通常被用于软件生命周期的不同阶段。当你发现两个不兼容的类必须同时工作时,就有必要使用适配器模式,其目的一般是为了避免代码重复。此处耦合不可预见。相反,桥接的使用者必须事先知道:一个抽象将有多个实现部分,并且抽象和实现两者是独立演化的。适配器模式在类已经设计好后实施;而桥接模式在设计类之前实施。这并不意味着适配器模式不如桥接模式,只是因为它们针对了不同的问题。
外观模式定义一个新的接口吗,而适配器则复用一个原有的接口。记住,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。
组装、装饰者 与 代理
组装模式和装饰者模式具有类似的结构图,这说明它们都基于递归组合来组织可变数目的对象。这一共同点可能会使你认为, 装饰者对象是一个退化的组装 , 但这一观点没有领会装饰者模式要点。相似点仅止于递归组合,同样,这是因为这两个模式的目的不同。
装饰者旨在使你能够不需要生成子类即可给对象添加职责。这就避免了静态实现所有功能组合,从而导致子类急剧增加。 组装则有不同的目的,它旨在构造类,使多个相关的对象能够以统一的方式处理,而多重对象可以被当作一个对象来处理。它重点不在于修饰, 而在于表示。
尽管它们的目的截然不同,但却具有互补性。因此组装和装饰者模式通常协同使用。在使用这两种模式进行设计时,我们无需定义新的类,仅需将一些对象插接在一起即可构建应用 。 这时系统中将会有一个抽象类 ,它有一些组装子类和装饰者子类 , 还有一些实现系统的基本构建模块。此时, 组装和装饰者将拥有共同的接口。从装饰者模式的角度看, 组装是一个ConcreteComponent 。而从组装模式的角度看,装饰者则是一个Leaf。当然,他们不一定要同时使用,正如我们所见,它们的目的有很大的差别。
另一种与装饰者模式结构相似的模式是代理 。 这两种模式都描述了怎样为对象提供一定程度上的间接引用,代理和装饰者对象的实现部分都保留了指向另一个对象的指针 , 它们向这个对象发送请求。然而同样,它们具有不同的设计目的。
像装饰者模式一样, 代理模式构成一个对象并为用户提供一致的接口。但与装饰者模式不同的是, 代理模式不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者,例如,实体在远程设备上,访问受到限制或者实体是持久存储的。
在代理模式中,实体定义了关键功能,而代理提供(或拒绝)对它的访问。在装饰者模式中,组件仅提供了部分功能,而一个或多个装饰者负责完成其他功能。装饰者模式适用于编译时不能(至少不方便)确定对象的全部功能的情况。这种开放性使递归组合成为装饰者模式中一个必不可少的部分。而在代理模式中则不是这样,因为代理模式强调一种关系(代理与它的实体之间的关系),这种关系可以静态的表达。
模式间的这些差异非常重要,因为它们针对了面向对象设计过程中一些特定的经常发生 问题的解决方法。但这并不意味着这些模式不能结合使用。可以设想有一个代理 - 装饰者 , 它可以给代理添加功能,或是一个装饰者 - 代理用来修饰一个远程对象 。 尽管这种混合可能有用(我们手边还没有现成的例子),但它们可以分割成一些有用的模式。