目录¶
1. 结构型设计模式简介¶
结构型设计模式(Structural Design Patterns)关注于如何将类或对象组合成更大的结构,以实现更复杂的功能。这些模式通过简化系统的结构,促进代码的复用、灵活性和可维护性。结构型模式通常处理类继承关系和对象组合关系。
2. 结构型设计模式概览¶
结构型设计模式主要包括以下七种模式:
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
组合模式(Composite Pattern)
装饰者模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
以下将对每种模式进行详细介绍,包括定义、UML 类图、适用场景、优缺点、常见误区与解决方案。
2.1. 适配器模式(Adapter Pattern)¶
定义¶
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以协同工作。适配器模式通过创建一个中间层(适配器),将客户端的请求转换为目标对象可以理解和处理的请求,从而实现接口的兼容。
UML 类图¶
+----------------+ +----------------+
| Client | | Target |
+----------------+ +----------------+
| | | + request() |
| | +----------------+
| | ^
| | |
| | +--------------------+
| | | Adapter |
| | +--------------------+
| | | - adaptee: Adaptee |
| | +--------------------+
| | | + request() |
+----------------+ +--------------------+
|
|
+---------------------+
| Adaptee |
+---------------------+
| + specificRequest() |
+---------------------+
适用场景¶
接口不兼容的类需要协同工作:当系统中存在需要使用的类,其接口与现有系统不兼容时,适配器模式提供了一种整合方式。
复用现有类,但其接口不符合需求:无需修改现有类的代码,通过适配器将其接口转换为客户端所需的接口。
系统需要使用一些现有的类,但这些类的接口不符合系统的需求:通过引入适配器,可以在不改变现有类的情况下,使其符合系统的接口要求。
希望为一个现有的类提供一个易于使用的接口:通过适配器类封装复杂的接口,使其更易于使用。
优缺点¶
优点 | 缺点 |
---|---|
- 增加了类的复用性 - 提高了类的灵活性和透明性 - 符合开闭原则 |
- 可能导致系统设计过度复杂 - 适配器与被适配类之间可能产生紧密耦合 |
常见误区与解决方案¶
误区1:过度使用适配器,导致系统中充斥着大量的适配器类,增加了系统复杂性。
解决方案:仅在必要时使用适配器,评估是否有更简洁的方式解决接口不兼容问题。
误区2:适配器与被适配类紧密耦合,适配器内部依赖被适配类的具体实现。
解决方案:通过依赖倒置原则,使适配器依赖于抽象接口,而非具体类。
2.2. 桥接模式(Bridge Pattern)¶
定义¶
桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与其实现部分分离,使它们可以独立地变化。桥接模式通过引入抽象接口和具体实现类的分离,提升了系统的灵活性和可扩展性。
UML 类图¶
+----------------+ +----------------+
| Abstraction |<>--------| Implementor |
+----------------+ +----------------+
| - implementor | | + operation() |
| + operation() | +----------------+
+----------------+ ^
^ |
| |
+----------------+ +----------------+
| RefinedAbs | | ConcreteImpl |
+----------------+ +----------------+
| + operation() | | + operation() |
+----------------+ +----------------+
适用场景¶
需要在抽象和具体实现之间增加更多的灵活性:当系统的抽象部分和实现部分可能独立变化时,桥接模式允许它们独立扩展。
希望抽象和实现可以独立扩展:通过桥接模式,可以在不影响另一部分的情况下扩展某一部分。
减少继承层次:通过组合替代继承,避免类层次过深,提升系统的可维护性。
优缺点¶
优点 | 缺点 |
---|---|
- 抽象与实现分离,增加了系统的可扩展性 - 遵循开闭原则 - 减少了类的层次结构 |
- 增加了系统的复杂性 - 需要开发者理解抽象与实现的分离概念 |
常见误区与解决方案¶
误区1:错误地认为桥接模式仅适用于接口转换。
解决方案:桥接模式更关注于抽象与实现的分离,而非仅仅接口转换。
误区2:在简单系统中引入桥接模式,导致设计过度复杂。
解决方案:评估系统的复杂性和未来的扩展需求,确保桥接模式的引入是必要的。
2.3. 组合模式(Composite Pattern)¶
定义¶
组合模式(Composite Pattern)是一种结构型设计模式,它将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。组合模式通过递归地组合对象,实现对复杂结构的统一管理和操作。
UML 类图¶
+---------------------+
| Component |
+---------------------+
| + operation(): void |
+---------------------+
^
|
+---------------------+ +-----------------------------+
| ConcreteComponent | | Composite |
+---------------------+ +-----------------------------+
| + operation(): void | | - children: List<Component> |
+---------------------+ +-----------------------------+
^ ^
| |
+---------------------+ +---------------------+
| Leaf | | Composite |
+---------------------+ +---------------------+
| + operation(): void | | + add(Component) |
| | | + remove(Component) |
+---------------------+ | + getChild(int) |
| + operation(): void |
+---------------------+
适用场景¶
需要表示部分-整体层次结构:当系统需要表示对象的层次结构时,如文件系统中的文件和文件夹。
客户需要统一对待单个对象和组合对象:客户端无需关心对象是单个的还是组合的,只需要以相同的方式处理它们。
构建树形结构:当系统需要构建复杂的树形结构时,组合模式提供了简单而灵活的解决方案。
希望简化客户端代码:通过组合模式,客户端代码可以更加简洁,不需要处理不同类型的对象。
优缺点¶
优点 | 缺点 |
---|---|
- 简化客户端代码 - 提高系统的灵活性 - 易于扩展 |
- 可能违反单一职责原则 - 系统可能变得复杂,难以理解对象的层次结构 |
常见误区与解决方案¶
误区1:认为组合模式只能用于树形结构,忽视了其在其他层次结构中的应用。
解决方案:理解组合模式的核心是“部分-整体”的关系,可以应用于多种层次结构中。
误区2:在不需要统一处理的情况下滥用组合模式,导致不必要的复杂性。
解决方案:仅在需要统一处理部分和整体时使用组合模式,避免过度设计。
2.4. 装饰者模式(Decorator Pattern)¶
定义¶
装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许在不改变现有对象结构的情况下,动态地给对象添加新的功能。装饰者模式通过创建装饰类来包装原有的类,从而在原有类的基础上扩展其功能。这种模式提供了比继承更灵活的方式来扩展对象的功能。
UML 类图¶
+---------------------+
| Component |
+---------------------+
| + operation(): void |
+---------------------+
^
|
+---------------------+ +-----------------------+
| ConcreteComponent | | Decorator |
+---------------------+ +-----------------------+
| + operation(): void | | - component: Component|
+---------------------+ +-----------------------+
^ ^
| |
+-------------------------+ +-------------------------+
| ConcreteDecoratorA | | ConcreteDecoratorB |
+-------------------------+ +-------------------------+
| + operation(): void | | + operation(): void |
| + addedBehavior(): void | | + addedBehavior(): void |
+-------------------------+ +-------------------------+
适用场景¶
需要在不影响其他对象的情况下,为对象动态添加职责:如为文本编辑器中的文本添加不同的格式(粗体、斜体、下划线)。
需要扩展类的功能,但不希望通过继承来实现:通过组合装饰者而非继承来添加新功能,避免类层次结构的复杂性。
需要动态地撤销已添加的功能:由于装饰者是可组合的,可以灵活地添加和移除装饰者来控制对象的功能。
遵循单一职责原则:通过多个装饰者,每个装饰者负责添加一种功能,保持职责的单一性。
优缺点¶
优点 | 缺点 |
---|---|
- 灵活地扩展对象功能 - 遵循开闭原则 - 避免了类爆炸 |
- 增加系统复杂性 - 可能导致调试困难 |
常见误区与解决方案¶
误区1:使用装饰者模式时,过度嵌套装饰者,导致代码难以理解和维护。
解决方案:限制装饰者的层级深度,或通过组合装饰者来管理复杂性,确保代码的可读性。
误区2:误用装饰者模式来实现简单的功能扩展,导致不必要的设计复杂性。
解决方案:评估功能扩展的复杂性,确定是否需要使用装饰者模式,避免过度设计。
2.5. 外观模式(Facade Pattern)¶
定义¶
外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。外观模式通过创建一个外观类,封装复杂的子系统,使客户端只需与外观类交互,而无需直接与子系统的各个部分打交道。
UML 类图¶
+------------------+ +------------------+
| Client | | Facade |
+------------------+ +------------------+
| | | - subsystem1 |
| | | - subsystem2 |
| | | - subsystem3 |
| | | + operation() |
+------------------+ +------------------+
| |
| |
v v
+------------------+ +------------------+ +------------------+
| Subsystem1 | | Subsystem2 | | Subsystem3 |
+------------------+ +------------------+ +------------------+
| + operation1() | | + operation2() | | + operation3() |
+------------------+ +------------------+ +------------------+
适用场景¶
简化复杂系统的接口:当你需要简化一个复杂子系统的接口,使得客户端更容易使用时。
降低系统的复杂性:通过提供一个统一的外观类,隐藏子系统的复杂性,减少客户端与子系统的依赖。
为多个子系统提供统一的入口:在需要多个子系统协同工作时,通过外观类协调各个子系统的交互。
系统独立于子系统的实现:客户端无需了解子系统的具体实现细节,只需通过外观类进行交互。
优缺点¶
优点 | 缺点 |
---|---|
- 简化接口 - 降低系统复杂性 - 提高系统的可维护性 |
- 可能导致系统设计过度简化 - 可能限制子系统的灵活性 |
常见误区与解决方案¶
误区1:将外观模式用于不需要隐藏子系统复杂性的场景,导致不必要的类增加。
解决方案:仅在确实需要隐藏子系统复杂性和简化接口时使用外观模式,避免过度设计。
误区2:在子系统内部仍然暴露过多的接口给客户端,未完全实现外观模式的优势。
解决方案:确保客户端仅通过外观类与子系统交互,子系统的内部细节对客户端透明。
2.6. 享元模式(Flyweight Pattern)¶
定义¶
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享大量细粒度对象以减少内存占用和提高性能。享元模式通过将对象的部分状态外部化,允许多个对象共享相同的内部状态,从而减少对象的数量。
UML 类图¶
+---------------------+ +----------------------+
| Client | | FlyweightFactory |
+---------------------+ +----------------------+
| - context1 | | - flyweights: Map |
| - context2 | +----------------------+
| + operation() | | + getFlyweight() |
+---------------------+ | + addFlyweight() |
| +----------------------+
| |
v v
+---------------------+ +----------------------+
| Context | | Flyweight |
+---------------------+ +----------------------+
| - externalState |<>------| + operation() |
+---------------------+ +----------------------+
| + operation() |
+---------------------+
^
|
+---------------------+
| ConcreteFlyweight |
+---------------------+
| - intrinsicState |
+---------------------+
| + operation() |
+---------------------+
适用场景¶
大量相似对象:系统中需要创建大量相似的对象,且这些对象的大部分状态可以共享。
内存资源有限:通过共享对象减少内存消耗,特别是在嵌入式系统或内存资源紧张的环境中。
性能优化:需要优化对象的创建和管理,以提高系统性能。
对象不可变:享元对象的内部状态通常是不可变的,可以安全地在多个上下文中共享。
优缺点¶
优点 | 缺点 |
---|---|
- 显著减少内存占用 - 提高性能 - 集中管理共享对象 |
- 增加设计复杂性 - 需要外部管理外部状态 - 对象共享限制(内部状态通常不可变) |
常见误区与解决方案¶
误区1:将享元模式用于无法共享内部状态的对象,导致无法有效减少对象数量。
解决方案:确保只有那些可以共享内部状态且外部状态可以独立管理的对象才使用享元模式。
误区2:过度外部化对象的状态,导致管理外部状态变得复杂。
解决方案:合理划分内部状态和外部状态,保持系统的可维护性和清晰性。
2.7. 代理模式(Proxy Pattern)¶
定义¶
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。通过代理对象,客户端可以在不直接访问目标对象的情况下,间接地执行目标对象的方法。代理模式在实际应用中常用于控制对象的访问、延迟对象的创建、记录对象的访问日志等。
UML 类图¶
+----------------+ +----------------+
| Client | | Subject |
+----------------+ +----------------+
| | | + request() |
| | +----------------+
| | ^
| | |
| | +----------------+
| | | RealSubject |
| | +----------------+
| | | + request() |
| | +----------------+
| |
| | +----------------+
| | | Proxy |
| | +----------------+
| | | - realSubject |
| | +----------------+
| | | + request() |
| | +----------------+
+----------------+
适用场景¶
需要控制对某个对象的访问:如在访问敏感资源时进行权限检查。
需要延迟对象的创建和初始化:如在需要时才创建大型对象,节省系统资源。
需要在对象访问前后执行额外的操作:如日志记录、缓存、性能监控等。
需要提供一个简化的接口:如为复杂子系统提供一个简单的访问接口。
需要远程访问对象:如在分布式系统中,通过代理对象实现远程方法调用。
优缺点¶
优点 | 缺点 |
---|---|
- 职责清晰 - 提高灵活性和可扩展性 - 隐藏目标对象的实现细节 |
- 增加系统复杂性 - 可能影响性能 - 需要维护代理类与目标类的一致性 |
常见误区与解决方案¶
误区1:将代理模式用于不需要控制访问的对象,导致不必要的类增加。
解决方案:仅在需要控制访问、延迟加载或增加额外功能时使用代理模式,避免过度设计。
误区2:代理对象与目标对象之间产生过多的依赖,降低系统的灵活性。
解决方案:通过依赖倒置原则,使代理对象依赖于抽象接口,而非具体类,降低耦合度。
3. 结构型设计模式的对比分析¶
为了更好地理解结构型设计模式的区别和联系,以下从意图与核心概念、结构与角色、适用场景、优缺点四个方面进行详细对比。
3.1. 意图与核心概念¶
模式名称 | 意图 |
---|---|
适配器模式 | 将一个类的接口转换成客户期望的另一个接口,解决接口不兼容的问题。 |
桥接模式 | 将抽象部分与其实现部分分离,使它们可以独立地变化。 |
组合模式 | 将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端对单个对象和组合对象具有一致性。 |
装饰者模式 | 动态地给对象添加职责,在不改变对象接口的情况下增强对象的功能。 |
外观模式 | 为子系统中的一组接口提供一个统一的高层接口,使子系统更易使用。 |
享元模式 | 通过共享大量细粒度对象以减少内存占用和提高性能。 |
代理模式 | 为其他对象提供一种代理,以控制对该对象的访问。 |
3.2. 结构与角色¶
模式名称 | 主要角色 | 角色说明 |
---|---|---|
适配器模式 | - Target - Client - Adaptee - Adapter |
Target:客户端期望的接口 Adaptee:已有但接口不兼容的类 Adapter:转换接口的类 |
桥接模式 | - Abstraction - RefinedAbstraction - Implementor - ConcreteImplementor |
Abstraction:抽象部分 Implementor:实现部分接口 RefinedAbstraction和ConcreteImplementor为扩展角色 |
组合模式 | - Component - Leaf - Composite |
Component:统一接口 Leaf:叶子节点 Composite:组合节点 |
装饰者模式 | - Component - ConcreteComponent - Decorator - ConcreteDecorator |
Component:统一接口 Decorator:持有Component引用 ConcreteDecorator:具体装饰者 |
外观模式 | - Facade - Subsystem Classes - Client |
Facade:统一高层接口 Subsystem Classes:子系统 Client:通过Facade访问子系统 |
享元模式 | - Flyweight - ConcreteFlyweight - FlyweightFactory - Context - Client |
Flyweight:享元接口 ConcreteFlyweight:具体享元 FlyweightFactory:管理享元 Context:外部状态 |
代理模式 | - Subject - RealSubject - Proxy - Client |
Subject:统一接口 RealSubject:真实对象 Proxy:代理对象 Client:通过Subject访问Proxy |
3.3. 适用场景¶
模式名称 | 适用场景 |
---|---|
适配器模式 | - 系统需要使用现有的类,但其接口不符合需求 - 引入一个新系统,且需要复用现有类 |
桥接模式 | - 需要在抽象和具体实现之间增加更多的灵活性 - 希望抽象和实现可以独立扩展 |
组合模式 | - 需要表示部分-整体层次结构 - 客户端需要统一对待单个对象和组合对象 |
装饰者模式 | - 需要在不改变对象接口的情况下动态地为对象添加职责 - 希望通过组合而非继承来扩展对象功能 |
外观模式 | - 需要简化复杂子系统的接口 - 客户端不需要了解子系统的内部细节 |
享元模式 | - 系统中存在大量相似对象,且可以共享其内部状态 - 内存或性能成为瓶颈 |
代理模式 | - 需要控制对对象的访问(如权限控制、日志记录) - 需要延迟对象的创建或加载 |
3.4. 优缺点比较¶
3.4.1. 适配器模式¶
优点 | 缺点 |
---|---|
- 增加了类的复用性 - 提高了类的灵活性和透明性 - 符合开闭原则 |
- 可能导致系统设计过度复杂 - 适配器与被适配类之间可能产生紧密耦合 |
常见误区与解决方案:
误区1:过度使用适配器,导致系统中充斥着大量的适配器类,增加了系统复杂性。
解决方案:仅在必要时使用适配器,评估是否有更简洁的方式解决接口不兼容问题。
误区2:适配器与被适配类紧密耦合,适配器内部依赖被适配类的具体实现。
解决方案:通过依赖倒置原则,使适配器依赖于抽象接口,而非具体类。
3.4.2. 桥接模式¶
优点 | 缺点 |
---|---|
- 抽象与实现分离,增加了系统的可扩展性 - 遵循开闭原则 - 减少了类的层次结构 |
- 增加了系统的复杂性 - 需要开发者理解抽象与实现的分离概念 |
常见误区与解决方案:
误区1:错误地认为桥接模式仅适用于接口转换。
解决方案:桥接模式更关注于抽象与实现的分离,而非仅仅接口转换。
误区2:在简单系统中引入桥接模式,导致设计过度复杂。
解决方案:评估系统的复杂性和未来的扩展需求,确保桥接模式的引入是必要的。
3.4.3. 组合模式¶
优点 | 缺点 |
---|---|
- 简化客户端代码 - 提高系统的灵活性 - 易于扩展 |
- 可能违反单一职责原则 - 系统可能变得复杂,难以理解对象的层次结构 |
常见误区与解决方案:
误区1:认为组合模式只能用于树形结构,忽视了其在其他层次结构中的应用。
解决方案:理解组合模式的核心是“部分-整体”的关系,可以应用于多种层次结构中。
误区2:在不需要统一处理的情况下滥用组合模式,导致不必要的复杂性。
解决方案:仅在需要统一处理部分和整体时使用组合模式,避免过度设计。
3.4.4. 装饰者模式¶
优点 | 缺点 |
---|---|
- 灵活地扩展对象功能 - 遵循开闭原则 - 避免了类爆炸 |
- 增加系统复杂性 - 可能导致调试困难 |
常见误区与解决方案:
误区1:使用装饰者模式时,过度嵌套装饰者,导致代码难以理解和维护。
解决方案:限制装饰者的层级深度,或通过组合装饰者来管理复杂性,确保代码的可读性。
误区2:误用装饰者模式来实现简单的功能扩展,导致不必要的设计复杂性。
解决方案:评估功能扩展的复杂性,确定是否需要使用装饰者模式,避免过度设计。
3.4.5. 外观模式¶
优点 | 缺点 |
---|---|
- 简化接口 - 降低系统复杂性 - 提高系统的可维护性 |
- 可能导致系统设计过度简化 - 可能限制子系统的灵活性 |
常见误区与解决方案:
误区1:将外观模式用于不需要隐藏子系统复杂性的场景,导致不必要的类增加。
解决方案:仅在确实需要隐藏子系统复杂性和简化接口时使用外观模式,避免过度设计。
误区2:在子系统内部仍然暴露过多的接口给客户端,未完全实现外观模式的优势。
解决方案:确保客户端仅通过外观类与子系统交互,子系统的内部细节对客户端透明。
3.4.6. 享元模式¶
优点 | 缺点 |
---|---|
- 显著减少内存占用 - 提高性能 - 集中管理共享对象 |
- 增加设计复杂性 - 需要外部管理外部状态 - 对象共享限制(内部状态通常不可变) |
常见误区与解决方案:
误区1:将享元模式用于无法共享内部状态的对象,导致无法有效减少对象数量。
解决方案:确保只有那些可以共享内部状态且外部状态可以独立管理的对象才使用享元模式。
误区2:过度外部化对象的状态,导致管理外部状态变得复杂。
解决方案:合理划分内部状态和外部状态,保持系统的可维护性和清晰性。
3.4.7. 代理模式¶
优点 | 缺点 |
---|---|
- 职责清晰 - 提高灵活性和可扩展性 - 隐藏目标对象的实现细节 |
- 增加系统复杂性 - 可能影响性能 - 需要维护代理类与目标类的一致性 |
常见误区与解决方案:
误区1:将代理模式用于不需要控制访问的对象,导致不必要的类增加。
解决方案:仅在需要控制访问、延迟加载或增加额外功能时使用代理模式,避免过度设计。
误区2:代理对象与目标对象之间产生过多的依赖,降低系统的灵活性。
解决方案:通过依赖倒置原则,使代理对象依赖于抽象接口,而非具体类,降低耦合度。
4. 结构型设计模式的选择指南¶
在实际开发中,选择合适的结构型设计模式取决于具体的需求和场景。以下是一些选择指南,帮助您在不同情况下选择合适的结构型设计模式:
接口不兼容,需整合不同系统或组件:
选择适配器模式。当您需要使用现有类,但其接口与系统需求不符时,适配器模式提供了一种整合方式。
需要在抽象和具体实现之间增加灵活性:
选择桥接模式。当系统的抽象部分和实现部分可能独立变化时,桥接模式允许它们独立扩展。
需要表示复杂的层次结构:
选择组合模式。当需要构建树形结构,统一处理单个对象和组合对象时,组合模式非常适用。
需要动态扩展对象功能:
选择装饰者模式。当需要在不修改对象接口的情况下,动态为对象添加功能时,装饰者模式提供了灵活的解决方案。
需要简化复杂系统的接口:
选择外观模式。当系统复杂,客户端需要一个简单的接口来访问子系统时,外观模式可以有效简化接口。
系统中存在大量相似对象,内存或性能成为瓶颈:
选择享元模式。当需要管理和复用大量相似对象时,享元模式通过共享减少内存占用。
需要控制对对象的访问:
选择代理模式。当需要在访问对象前后执行额外操作,或需要延迟对象创建时,代理模式是理想选择。
5. 总结¶
结构型设计模式通过优化类和对象的组合方式,提升系统的灵活性、复用性和可维护性。每种结构型模式都有其特定的应用场景和优势,理解它们的意图、结构及适用场景是有效应用这些模式的关键。
关键学习点回顾:
理解各结构型模式的核心概念:
适配器模式解决接口不兼容问题。
桥接模式分离抽象与实现。
组合模式构建部分-整体层次结构。
装饰者模式动态扩展对象功能。
外观模式简化复杂系统的接口。
享元模式通过共享减少内存占用。
代理模式控制对对象的访问。
掌握各模式的结构与角色:了解每种模式涉及的主要角色及其职责,有助于在设计中正确应用模式。
识别适用的应用场景:根据具体需求选择合适的结构型设计模式,确保设计的合理性和高效性。
认识各模式的优缺点:权衡模式的优势与潜在的复杂性,合理选择和应用。