结构型模式(上)

131 阅读10分钟

结构型模式关注如何将现有类或对象组织在一起形成更加强大的结构

image.png

适配器模式

在适配器模式中引入了一个被称为适配器的包装类,而它所包装的对象被称为适配者,即被适配的类

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

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器模式和类适配器模式两种

  • 在对象适配器模式中,适配器和适配者之间是关联关系
  • 在类适配器模式中,适配器和适配者之间是继承/实现关系

image.png

在对象适配器模式结构图中包含 3 个角色

  • 目标抽象类(Target),定义客户所需要接口,可以是一个抽象类或接口,也可以是具体类
  • 适配器类(Adapter),适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配。适配器类是适配器模式的核心,在对象适配器模式中,它通过继承 Target 并关联一个 Adaptee 对象使二者产生联系
  • 适配者类(Adaptee),适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配

类适配器模式和对象适配器模式最大的区别在于其适配器和适配者之间的关系是继承关系

image.png


在对象适配器模式的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器

image.png


缺省适配器模式,当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性的覆盖父类的某些方法来实现需求。它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式

image.png

缺省适配器模式包含 3 个角色

  • 适配者接口(ServiceInterface),它是一个接口,通常在该接口中声明了大量的方法
  • 缺省适配器类(AbstractServiceClass),它是缺省适配器模式的核心类,使用空方法的形式实现了在 ServiceInterface 接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义
  • 具体业务类(ConcreteServiceClass),它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中定义所有方法,而对于一些无需使用的方法也不得不提供空实现

适配器模式的优点

  • 将目标类和适配者类接口。通过引入一个适配器类来重用现有的适配者类,无需修改原有结构
  • 增加了类的透明性和复用性
  • 灵活性和扩展性都非常好

对象适配器模式还有如下优点

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标
  • 可以适配一个适配类的子类

适配器模式的缺点

类适配器模式的缺点

  • 对于不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者
  • 适配者类不能为最终类
  • 在 Java 等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性

对象适配器模式的缺点

  • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦
  • 如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,在子类中将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂

适配器模式的适用场景

  • 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作

桥接模式

如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合单一职责原则。

和多层继承方案不同,它将两个独立变化的维度设计成两个独立的继承等级结构,并且在抽象层建立一个抽象关系,该关联关系类似一条连接两个独立继承结构的桥,故名为桥接模式

桥接模式,将抽象部分和其实现部分分离,使它们都可以独立的变化。它是一种对象结构型模式,又称为柄体模式或接口模式

image.png

在桥接模式结构图中包含 4 个角色

  • 抽象类(Abstraction),用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个实现类接口(Implementor)类型的对象并可以维护该对象
  • 扩充抽象类(RefinedAbstraction),扩充由抽象类(Abstraction)定义的接口,通常情况下它不再是抽象类而是具体类
  • 实现类接口(Implementor),定义实现类的接口,这个接口不一定要与抽象类(Abstraction)的接口完全一致,事实上这两个接口可以完全不同
  • 具体实现类(ConcreteImplementor),具体实现实现类接口,在不同的具体实现类(ConcreteImplementor)中提供基本操作的不同实现

在桥接模式中体现了很多面向对象设计原则的思想,包括单一职责原则、开闭原则、合成复用原则、里氏替换原则、依赖倒转原则等

桥接模式的优点

  • 分离抽象接口以及其实现部分
  • 在很多情况下,桥接模式可以取代多层继承方案
  • 桥接模式提高了系统的可扩展性

桥接模式的缺点

  • 桥接模式的使用会增加系统的理解和设计难度
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性

桥接模式的适用场景

  • 如果一个系统需要在抽象类和具体类之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
  • 抽象部分和实现可以以继承的方式独立扩展而互不影响,在程序运行时可以动态地将一个抽象类子类的对象和一个实现类子类的对象进行组合
  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展
  • 对于那些不希望使用继承或者因为多层继承导致系统类的个数急剧增加的系统,桥接系统尤为适用

组合模式

组合模式,组合多个对象形成树形结构以表示具有「部分-整体」关系的层次结构。组合模式对单个对象和组合对象的使用具有一致性,又可以称为「部分-整体」模式,它是一种对象结构型模式

image.png

在组合模式结构图中包含 3 个角色

  • 抽象构件(Component),它可以是接口或者抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问以及管理它的子构件的方法,例如增加子构件、删除子构件、获取子构件等
  • 叶子构件(leaf),它在组合模式结构中表示叶子节点对象。它负责实现抽象构件中定义的行为
  • 容器构件(Composite),它在组合模式结构中表示容器节点对象。容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点

在使用组合模式时,根据抽象构建类的定义形式,可以将组合模式分为透明组合模式和安全组合模式

  • 透明组合模式,抽象构件中申明所有用于管理成员对象的方法,确保所有的构建类都有相同的接口。透明组合模式的缺点是不够安全,因为叶子节点和容器节点在本质上是有区别的。叶子对象不可能有下一个层次的对象,因此提供的 add()、remove() 等方法是没有意义的

image.png

  • 安全组合模式,在抽象构件中没有声明任何用于管理成员对象的方法,而是在容器构件类中声明并实现这些方法。安全组合模式的缺点是不够透明。因为叶子构件和容器构件具有不同的方法,而且容器构件那些用于管理成员对象的方法没有在抽象构件类中定义,因此,客户端不能完全针对抽象编程,必须区分对待叶子构件和容器构件

image.png

组合模式的优点

  • 组合模式可以清楚的定义分层次的复杂对象,表示对象的全部或者部分层次
  • 客户端可以一致的使用一个组合结构或者其中单个对象,不必关心处理的是单个对象还是整个对象结构
  • 在组合模式中增加新的容器构件和叶子构件都很方便,无需对现有类库进行任何修改,符合开闭原则
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案

组合模式的缺点

  • 在增加新构件时很难对容器中的构件类型进行限制,必须通过在运行时进行类型检查来实现,实现过程较为复杂

组合模式的适用场景

  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体和部分的差异,客户端可以一致性的对待它们
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,将来需要增加一些新的类型

结构型模式(下)