设计模式 - 结构型模式 | 青训营笔记

94 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 20 天

结构型模式

  • 将不同的类和对象组合在一起

  • 结构性模式主要分为:类结构型模式对象结构型模式

  • 类结构模式:一般只存在继承实现关系

  • 对象结构模式:通过关联关系,在一个类的实例中使用另一个类的实例

  • 为了遵循合成复用原则尽量使用关联关系代替继承关系,因此大部分结构型模式都是对象结构型模式

适配器模式

别名:包装器(Wrapper)模式

定义:将一个接口装换成客户希望的另一个接口使得接口不兼容的类可以一起工作

描述:适配器模式可以看作是现有系统进行不久以及对现有类进行重用的模式。或者是第三方接口可以满足本系统的需求,但是接口并不符合本系统的接口规范,这个时候就需要使用适配器模式对第三方接口进行适配。

类图:

  • 类适配器

    类适配器

  • 对象适配器

    对象适配器

优点:

  • 目标类和适配者的解耦,通过引入一个适配器重用现有功能代码
  • 增加了类的透明性和复用性,提高了适配者的复用性
  • 灵活性和扩展性非常好,更换适配器,符合开闭原则
  • 类适配器:由于继承关系,置换一些适配者的方法会很方便
  • 对象适配器:可以适配存在继承层次的适配者,或者把多个不同的适配者适配到同一个目标

缺点:

  • 类适配器模式
    • 一次最多适配一个适配者类,不能同时适配多个适配者(多继承也不过是隔靴搔痒)
    • 适配者不能是最终类(final)
    • 目标抽象类只能为接口,不能为类(有多继承机制就行)
  • 对象适配器模式
    • 在适配器中置换适配者类的某些方法比较麻烦

场景:

  • 需要使用一些现有的类,但是这些类的接口符合系统需要
  • 创建一个可复用的类

扩展:

  • 缺省适配器模式(Default Adapter Pattern)

    不需要实现要给接口所提供的所有方法时,可以设计一个抽象类实现这个接口

  • 双向适配器模式

桥接模式

别名:柄体(Handle and Body)模式或接口(Interface)模式

描述:使用抽象关联取代了传统的多继承,将类之间的静态继承关系转换为动态的对象组合关系

定义:将抽象部分与它的实现部分分离,使得他们都有独立的继承体系。

类图:

桥接模式

优点:

  • 脱耦:将强关联关系转换弱关联关系,符合合成复用原则
  • 分离抽象接口极其实现部分
  • 取代继承的方案,使得Java也能有更加灵活的多继承方案,并且极大减少了子类的个数
  • 提高了系统的可扩展性,符合开闭原则

缺点:

  • 增加系统的理解和设计难度,由于关联关系建立在抽象层,要求开发者一开始就要对抽象层进行设计和编程
  • 正确识别出独立变化的维度并不是一件容易的事情

场景:

  • 需要在抽象和实现层面增加更多的灵活性,避免两个层次之间存在静态的继承关系
  • 抽象部分和实现部分可以一继承的方式独立扩展而互不影响
  • 一个类存在两个独立变化的维度
  • 不希望使用继承或者因为多层继承导致系统类的个数急剧增加

组合模式

描述:组合模式使用面向对象的方式来处理树形结构;使用了递归调用的机制来对整个结构进行处理

定义:组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对**单个对象(即叶子对象)组合对象(即容器对象)**的使用具有统一性。

类图:

组合模式

优点:

  • 定义分层次的复杂对象,表示对象的全部或者部分层次;让客户端忽略层次之间的差异,方便对整个层次结构进行控制
  • 客户端可以一致使用一个组合结构或者其中单个对象,不必关心处理的是单个对象还是组合结构,简化客户端代码
  • 增加新的容器或者新的构件都很方便,符合开闭原则
  • 树形结构的面向对象实现提供了一种灵活方案

缺点:

  • 设计更加抽象,理解难度大
  • 增加新构件的时候,很难对容器中的构建类型进行限制

场景:

  • 具有整体和部分的层次结构中使用,而且客户端希望能够以一种一致地对待他们
  • 面向对象语言中,需要处理一种树形结构,而且每个阶节点有自己的状态和行为
  • 在系统中能够分离处理叶子对象和容器对象,而且他们的类型并不确定

扩展:

  • 透明组合模式
  • 安全组合模式

装饰器模式

动机:

  • 希望在原有对象的功能基础上给新的对象增加一些新的行为
  • 用于替代继承的一种技术,给对象动态增加职责,使用关联关系取代对象之间的继承关系
  • 在装饰器方法中,既可以调用被修饰对象的方法,也可以在这个基础上增加新的方法,扩展原有功能

定义:动态地给对象增加一些额外的职责,就增加对象来说,装饰器模式比生成子类实现更为灵活

  • 对客户以透明的方式,给一个对象附加更多的责任
  • 可以在不需要创建新的子类的情况下,让对象的功能扩展

类图:

装饰器模式

分析:

透明装饰器模式

  • 要求客户端完全针对抽象编程,不应该将对象声明为具体的类型或者装饰器类型,应该全部声明为抽象的构件类型
  • 对于客户端而言,具体构建对象和具体装饰器对象没有任何区别。可以对一个已经被装饰过的对象进行多次装饰
  • 无法在客户端单独调用新增的方法

半透明装饰器模式

  • 具体装饰类型来定义装饰之后的对象,而具体构建对象使用抽象构件来声明
  • 具体构建类型无需关心,是透明的;但是具体的装饰类必须指定,是不透明
  • 可以单独调用新增的方法(职责)
  • 不能对一个对象多次装饰,而且客户端要区别对待具体构件对象和装饰后的对象

优点:

  • 装饰器模式比继承更加灵活,不会导致子类个数急剧飙升
  • 装饰器模式,可以动态扩展一个对象的功能
  • 一个装饰器可以多次被装饰(透明装饰)
  • 具体构件和具体装饰器可以独立变化,用户根据需要增加具体的新构件或者具体的装饰器类,符合开闭原则

缺点:

  • 使用装饰器模式,将产生很多小对象,在一定长度上会影响程序的性能
  • 比继承更容易出错,排错也更加困难

场景:

  • 以动态透明的方式给单个对象添加职责
  • 不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护的时候,可以使用装饰模式

外观模式

描述:为复杂的系统提供统一的入口

定义:外部与子系统通信通过一个统一的外观对象进行,为子系统中的一组接口提供一个统一的入口

类图:

模式结构

分析:

  • 迪米特法则的具体实现
  • 通过引入一个新的外观类降低原有系统的使用难度降低客户和子系统的耦合程度

优点:

  • 减少了客户端所需要处理的对象数量
  • 实现了客户端和子系统之间的松耦合关系
  • 子系统的修改对其他子系统之间没有任何影响,子系统内部的修改影响到外观对象

缺点:

  • 不能很好的限制客户端直接使用子类系统
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码

场景:

  • 为一系列复杂的子系统提供统一的简单入口
  • 减少客户端和子系统之间的复杂依赖关系
  • 层次化系统结构中,外观模式可以为每一个层次的系统定义一个入口,不同的系统之间不直接相互联系,而是通过外观类建立联系,降低层次之间的耦合度

模式扩展:

  • 一个系统有多个外观类

  • 不要试图通过外观类为子系统增加新行为

  • 外观模式与迪米特法则

  • 抽象外观类的引入

    如果需要增加,删除或者更换与外观类交互的子系统,必须修改外观类或者客户端的源代码,这违背了开闭原则,因此可以通过引入抽象外观类对系统进行改进,就可以一定程度上解决这个问题

代理模式

动机:引入一个新的对象来实现对真实对象的操作,或者将新的对象作为真实对象的一个替身;引入一个代理对象来访问一个对象

定义:给某个对象提供一个代理,并由代理对象控制原对象的引用

类图:

代理模式

几种常见的代理模式:

  • 远程代理(Remote Proxy)
  • 虚拟代理(Virtual Proxy)
  • 保护代理(Protected Proxy):不同用户提供不同访问权限
  • 缓冲代理(Cache Proxy)
  • 智能引用代理(Smart Reference Proxy)

优点:

  • 协调调用者和被调用者,降低了系统的耦合度
  • 增加和更换代理类无需修改源代码,符合开闭原则

缺点:

  • 有些类型的代理模式可能造成请求的处理速度慢(保护代理
  • 有些代理模式实现过程可能较为复杂(远程代理