结构型设计模式

面向对象的软件系统中,每个类/对象都承担了一定的职责,它们可以相互协作,实现一些复杂的功能。结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构。不同的结构型模式从不同的角度来组合类或对象,在尽可能满足各种面向对象设计原则的同时,为类或对象的组合提供一系列巧妙的解决方案。

结构型设计模式包括适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰模式(Decorator Pattern)、外观模式(Paacade Pattern)、享元模式(Flyweight Pattern)和代理模式(Proxy Pattern)

适配器模式

动机

解决需要合作的类的接口不兼容问题

概念

适配器模式(Adapter Pattern): 将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
设计模式分类

优缺点

优点:

  • 将目标类和适配器者类解耦,通过引入一个适配器类来重用现有的适配器类,无须修改现有结构
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者类的通用性,同一个适配者类可以在多个不同的系统中复用
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则

缺点:

  • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者
  • 适配者类不能为最终类,例如在Java中不能为final类,C#中不能为sealed类
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性
适用场景
  • 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作

桥接模式

动机

处理多变化维度的情况,将继承关系转变为关联关系,从而降低类与类之间的耦合

概念

桥接模式(Bridge Pattern):将抽象部分与其实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
设计模式分类

优缺点

优点:

  • 分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化(即抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任意组合子类,从而获得多维度组合对象)
  • 在很多情况下,桥接模式可以取代多层继承方案。多层继承方案违背了单一职责原则,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大地减少了子类的个数
  • 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则

缺点:

  • 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累
适用场景
  • 如果一个系统需要在抽象类和具体类之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式尅使它们在抽象层建立一个关联关系
  • 抽象部分和实现部分可以以继承的方式独立扩展而互不影响,在程序运行时可以动态地将一个抽象类子类的对象和一个实现子类的对象进行组合,即系统需要对抽象类角色和实现类角色进行动态耦合
  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
  • 对于那些不希望使用继承或因为多层继承导致系统类个数急剧增加的系统,桥接模式尤为适用

组合模式

动机

组合模式可以处理树形结构或者树形结构的一部分,也可以处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)

概念

组合模式(Composite Pattern): 组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以成为“整体-部分”(Part-Whole)模式,它是一总会给你对象结构型模式。
设计模式分类

优缺点

优点:

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端的代码
  • 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行然和修改,符合开闭原则
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的属性结构,但对树形结构的控制却非常简单

缺点:

  • 在增加新构件时很难对容器中的构建类型进行限制。有时希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂
适用场景
  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致性对待他们
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

装饰模式

动机

对已有对象的功能进行扩展(装饰),以获得更加符合用户需求的对象,使得对象具有更加强大的功能

概念

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。
设计模式分类

优缺点

优点:

  • 对于扩展一个对象的功能,装饰模式比继承更具灵活性,不会导致类的个数急剧增加
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为
  • 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多具有不同行为的组合,得到功能更加强大的对象
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合开闭原则。

缺点:

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程度上影响程序的性能
  • 装饰模式提供了一种比继承更加灵活的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
适用场景
  • 在不影响其它对象的情况下,以动态、透明的方式给单个对象增加职责
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类已定义为不能被继承(如Java语言中的final类)

外观模式

动机

在软件开发中,有时为了完成一项较为复杂的功能,一个类需要和多个其它业务类交互,而这些需要交互的业务类经常会作为一个完整的整体出现,由于涉及的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而使用这些业务类的类只需和该类交互即可。外观模式通过引入一个新的外观类来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。

概念

外观模式(Facade Pattern): 外观与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口,外观模式定义了一个高层接口,这个接口使得这一系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
设计模式分类

优缺点

优点:

  • 对客户端屏蔽子系统组件,减少了客户端所需处理的对象数目使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  • 实现子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  • 一个子系统的修改对其它子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
  • 只是提供了一个访问子系统的统一入口,并不影响客户端直接使用子系统类。

缺点:

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,这违背了开闭原则。
适用场景
  • 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  • 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

享元模式

动机

在软件系统中,有时候也会存在资源浪费的情况,例如,在计算机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运行代价过高,内存属于计算机的“稀缺资源”,不应该“随便浪费”。享元模式就是针对此种情况,实现对这些相同 或者相似对象的共享访问的。

概念

享元模式(Flyweight Pattern): 运用共享技术有效地支持大量细粒度对象的服用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,是一种对象结构型模式。
设计模式分类

优缺点

优点:

  • 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

缺点:

  • 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
适用场景
  • 一个系统中有大量相同或相似的对象,造成内存的大量浪费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,在需要多次重复使用享元对象时才值得使用享元模式。

代理模式

动机

在软件开发中,有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想活不能直接访问某个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。

概念

代理模式(Proxy Pattern): 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
设计模式分类

优缺点

优点:

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度,满足迪米特法则。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
  • 远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
  • 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  • 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

缺点:

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂,例如远程代理。
适用场景
  • 当客户端对象需要访问远程主机中的对象时,可以使用远程代理。
  • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时,可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
  • 当需要控制一个对象的访问,为不同用户提供不同级别的访问权限是,可以使用保护代理。
  • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果是,可以使用缓冲代理,通过缓冲代理,系统无须再客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
  • 当需要为一个对象的访问(引用)提供一些额外的操作时,可以使用智能引用代理。