大家好,这里是编程Cookbook,关注公众号「编程Cookbook」,获取更多面试资料。本文是对设计模式中创建模式的详细讲解,共7种,分别是适配器模式、桥接模式、组合模式、装饰器模式、代理模式、外观模式、享元模式。
常用结构型模式
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
适配器模式(Adapter Pattern)
适配器模式是一种结构型设计模式,它允许将一个类的接口转换成另一个接口。适配器模式的核心思想是解决接口不兼容的问题,使得原本由于接口不匹配而无法一起工作的类可以协同工作。
组成成分
适配器模式通常包含以下角色:
-
目标接口(Target):
- 客户端期望的接口,定义了客户端需要使用的功能。
-
适配者(Adaptee):
- 需要被适配的类,通常是已经存在的类,但其接口与目标接口不兼容。
-
适配器(Adapter):
- 适配器的核心类,负责将适配者的接口转换成目标接口。
- 适配器可以是
类适配器
(通过继承实现)或对象适配器
(通过组合实现,后面会讲解组合模式)。
特点
优点
- 解耦:将客户端与适配者解耦,客户端只需要依赖目标接口。
- 复用性:可以复用现有的类,无需修改其代码。
- 灵活性:可以动态适配不同的适配者。
缺点
- 复杂度增加:引入了额外的适配器类,增加了系统的复杂度。
- 过度使用问题:如果系统中大量使用适配器,可能会导致代码难以理解和维护。
使用场景
-
接口不兼容:
- 当需要使用一个已经存在的类,但其接口与系统要求的接口不匹配时。
-
复用现有类:
- 当需要复用一些现有的类,但这些类的接口不符合系统的需求时。
-
统一接口:
- 当需要统一多个类的接口,以便客户端可以统一调用时。
-
第三方库集成:
- 当需要集成第三方库或组件,但其接口与系统不兼容时。
实现方式
1. 类适配器
- 定义:通过继承适配者类来实现适配器。
- 特点:适配器类既是目标接口的实现类,又是适配者类的子类。
- 适用场景:适配者类的方法可以直接被复用。
2. 对象适配器
- 定义:通过组合适配者对象来实现适配器。
- 特点:适配器类持有适配者对象的引用,并通过调用适配者对象的方法来实现目标接口。
- 适用场景:适配者类的方法不能直接复用,或者需要适配多个适配者类。
例子
- 场景:你有一个旧的打印机类(
OldPrinter
),它有一个printDocument()
方法,但你的新系统需要一个print()
方法。 - 解决方案:创建一个适配器类,将
printDocument()
方法适配为print()
方法。 - 应用:在集成第三方库或旧系统时,适配器模式非常有用。
总结
- 适配器模式通过转换接口,使得不兼容的类可以协同工作,适用于接口不兼容、复用现有类或集成第三方库的场景。
- 适配器模式分为类适配器和对象适配器,前者通过继承实现,后者通过组合实现。
- 适配器模式能够有效提升系统的灵活性和复用性,但需要注意避免过度使用,以免增加系统复杂度。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
桥接模式(Bridge Pattern)
桥接模式是一种结构型设计模式,它将抽象部分与实现部分 分离,使它们可以独立变化(通过组合方式连接抽象实现)。桥接模式的核心思想是通过组合代替继承,从而避免类爆炸问题,并提高系统的灵活性和可扩展性。
组成成分
桥接模式通常包含以下角色:
-
抽象部分(Abstraction):
- 定义抽象接口,通常包含对实现部分的引用。
- 可以是抽象类或接口。
-
扩展抽象部分(Refined Abstraction):
- 对抽象部分的扩展,提供更具体的功能。
-
实现部分(Implementor):
- 定义实现类的接口,通常是一个抽象类或接口。
-
具体实现部分(Concrete Implementor):
- 实现 实现部分的接口,提供具体的实现。
特点
优点
- 解耦抽象与实现:抽象部分和实现部分可以独立变化,互不影响。
- 提高扩展性:可以独立扩展抽象部分和实现部分,避免类爆炸问题。
- 符合开闭原则:新增抽象或实现时,无需修改现有代码。
缺点
- 复杂度增加:引入了更多的类和接口,增加了系统的复杂度。
- 设计难度较高:需要正确识别抽象部分和实现部分,设计不当可能导致系统难以理解。
使用场景
-
抽象与实现需要独立变化:
- 当抽象部分和实现部分需要独立扩展时,可以使用桥接模式。
-
避免类爆炸:
- 当使用继承会导致类层次结构过于复杂时,可以使用桥接模式。
如果直接使用继承,当每个维度有多个变化时,可能会导致子类数量呈指数增长。
-
跨平台开发:
- 当需要开发跨平台应用时,可以使用桥接模式将平台相关代码与业务逻辑分离。
例子
- 场景:你有一个图形绘制系统,支持多种形状(如圆形、矩形)和多种颜色(如红色、蓝色)。
- 解决方案:将形状和颜色分离,通过桥接模式组合它们,避免类数量爆炸。
- 应用:适用于多个维度变化的场景,如 GUI 库中的控件和主题。
总结
- 桥接模式通过将抽象部分与实现部分分离,使它们可以独立变化,适用于抽象与实现需要独立扩展的场景。
- 桥接模式能够有效避免类爆炸问题,并提高系统的灵活性和可扩展性。
- 实现时需要注意正确识别抽象部分和实现部分,避免设计不当导致系统复杂度增加。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
组合模式(Composite Pattern)
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式的核心思想是统一对待单个对象和组合对象,使得客户端可以一致地处理它们。
组成成分
组合模式通常包含以下角色:
-
组件(Component):
- 定义所有对象的通用接口,可以是抽象类或接口。
- 声明了组合对象和叶子对象的共同操作。
-
叶子(Leaf):
- 表示树形结构中的叶子节点,没有子节点。
- 实现了组件接口的具体操作。
-
组合(Composite):
- 表示树形结构中的分支节点,包含子节点。
- 实现了组件接口,并管理子节点(添加、删除、遍历等)。
特点
优点
- 简化客户端代码:客户端不需要关心处理的是单个对象还是组合对象。
- 易于扩展:可以很容易地增加新的组件类型。
缺点
- 设计复杂度增加:需要正确设计组件接口,确保所有组件类型都能实现。
- 类型检查问题:在某些语言中,可能需要运行时类型检查来确保操作的安全性。
使用场景
-
树形结构:
- 当需要表示对象的“部分-整体”层次结构时,如文件系统、组织架构等。
-
统一处理:
- 当希望客户端能够一致地处理单个对象和组合对象时。
-
递归结构:
- 当需要处理递归结构时,如菜单、目录等。
-
动态组合:
- 当需要在运行时动态组合对象时。
例子
- 场景:你需要处理文件系统中的文件和文件夹。文件夹可以包含文件或其他文件夹。
- 解决方案:使用组合模式,将文件和文件夹统一为“组件”,用户可以递归地操作整个结构。
- 应用:适用于树形结构数据的场景,如组织机构、菜单系统等。
总结
- 组合模式通过将对象组合成树形结构,使得客户端可以一致地处理单个对象和组合对象,适用于表示“部分-整体”层次结构的场景。
- 组合模式能够简化客户端代码,并提高系统的灵活性和可扩展性。
- 实现时需要注意正确设计组件接口,确保所有组件类型都能实现,并处理好递归结构。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
装饰器模式(Decorator Pattern)
装饰器模式是一种结构型设计模式,它允许动态地为对象添加职责,而无需修改其代码。装饰器模式的核心思想是通过组合代替继承,从而在不改变对象结构的情况下扩展其功能。
组成成分
装饰器模式通常包含以下角色:
-
组件接口(Component):
- 定义所有对象的通用接口,可以是抽象类或接口。
-
具体组件(Concrete Component):
- 实现组件接口的具体类,是被装饰的原始对象。
-
装饰器(Decorator):
- 持有组件接口的引用,并实现组件接口。
- 可以在调用组件接口的方法前后添加额外的行为。
-
具体装饰器(Concrete Decorator):
- 实现装饰器接口,为组件添加具体的职责。
特点
优点
- 动态扩展:可以在运行时动态地为对象添加职责。
- 符合开闭原则:新增功能时无需修改现有代码。
- 灵活性高:可以通过组合不同的装饰器来实现复杂的功能。
缺点
- 复杂度增加:引入了更多的类和对象,增加了系统的复杂度。
- 设计难度较高:需要正确设计装饰器接口,确保所有装饰器都能正确扩展组件。
使用场景
-
动态扩展功能:
- 当需要动态地为对象添加功能时,可以使用装饰器模式。
-
避免类爆炸:
- 当使用继承会导致类层次结构过于复杂时,可以使用装饰器模式。
-
不可修改的类:
- 当需要扩展一个不可修改的类时,可以使用装饰器模式。
-
多重功能组合:
- 当需要组合多个功能时,可以使用装饰器模式。
例子
- 场景:你有一个咖啡类,需要动态地添加配料(如糖、牛奶)。
- 解决方案:使用装饰器模式,将配料作为装饰器类,动态地添加到咖啡对象中。
- 应用:适用于需要动态扩展对象功能的场景,如 Java 的 I/O 流。
总结
- 装饰器模式通过动态地为对象添加职责,使得系统更加灵活和可扩展,适用于需要动态扩展功能的场景。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
代理模式(Proxy Pattern)
代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式的核心思想是通过代理对象间接访问目标对象,从而可以在访问目标对象时添加额外的控制逻辑。
代理模式的类型
-
静态代理
- 在编译时创建代理类,代理类与目标类实现相同的接口。
-
动态代理
- 在运行时动态生成代理类,适用于无需事先定义代理类的场景。
特点
优点
- 控制访问:可以在访问目标对象时添加额外的控制逻辑。
- 解耦:将客户端与目标对象解耦,客户端只需要与代理交互。
- 增强功能:可以在不修改目标对象的情况下增强其功能。
缺点
- 复杂度增加:引入了额外的代理类,增加了系统的复杂度。
- 性能开销:代理模式可能会引入额外的性能开销。
使用场景
以下是代理模式使用场景的简要介绍:
-
远程访问
当客户端需要访问位于远程的对象时,使用远程代理。远程代理处理与远程对象的通信细节,对客户端来说就像在访问本地对象一样。 -
延迟加载
对于创建开销大的对象,采用虚拟代理实现延迟加载。在真正需要使用对象之前,虚拟代理代替真实对象占位,实际使用时才创建真实对象,避免不必要的资源消耗。 -
权限控制
若要控制对某些敏感对象的访问权限,可利用保护代理。保护代理根据请求方的权限决定是否允许其访问对象,保障资源安全。 -
智能引用
当希望在访问对象时执行额外操作,例如对象访问计数、缓存机制、资源清理等,智能引用代理就可派上用场。它在对象访问过程中实现这些附加功能。
例子
- 场景:你需要加载网络图片,但图片加载较慢,希望在加载完成前显示占位符。
- 解决方案:使用代理模式,代理对象在图片加载完成前显示占位符,加载完成后替换为真实图片。
- 应用:适用于远程代理、虚拟代理、保护代理等场景。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
适配器模式、桥接模式、组合模式、装饰器模式、代理模式的对比
模式 | 核心思想 | 适用场景 | 主要区别 |
---|---|---|---|
适配器模式 | 将一个类的接口转换成客户端期望的另一个接口。 | 接口不兼容,需要复用现有类。 | 通过适配器转换接口,解决接口不兼容问题。 |
桥接模式 | 将抽象部分与实现部分分离,使它们可以独立变化。 | 抽象与实现需要独立变化,避免类爆炸。 | 通过分离抽象与实现,使它们可以独立扩展。 |
组合模式 | 将对象组合成树形结构以表示“部分-整体”的层次结构。 | 需要处理树形结构的数据。 | 通过树形结构统一处理单个对象和组合对象。 |
装饰器模式 | 动态地为对象添加职责,不改变其接口。 | 需要动态扩展对象功能。 | 通过组合扩展对象功能,不改变接口。 |
代理模式 | 为其他对象提供一种代理以控制对这个对象的访问。 | 需要控制对象的访问或延迟加载。 | 通过代理控制对象的访问,通常不改变接口。 |
总结
- 适配器模式:解决接口不兼容问题,适用于复用现有类。
- 桥接模式:分离抽象与实现,适用于抽象与实现需要独立变化的场景。
- 组合模式:处理树形结构,适用于“部分-整体”层次结构的场景。
- 装饰器模式:动态扩展对象功能,适用于需要动态添加职责的场景。
- 代理模式:控制对象访问,适用于需要延迟加载、权限控制等场景。
每种模式都有其特定的应用场景和核心思想,理解它们的区别有助于在实际开发中正确选择和使用设计模式。
非常用结构型模式
外观模式(Facade Pattern)
外观模式是一种结构型设计模式,旨在为复杂的子系统提供一个简化的接口。通过引入一个外观类,客户端只需与外观类交互,而无需直接与子系统的多个类打交道。
组成成分
- 外观类(Facade):定义一个更高级别的接口,用于封装对子系统的访问。
- 子系统类(Subsystem Classes):实现系统的具体功能,通过外观类对外暴露。
特点
- 简化接口:外观模式通过提供一个简化的接口,隐藏了系统的复杂性,使得客户端更容易使用。
- 解耦:客户端与子系统之间的耦合度降低,子系统的变化不会直接影响客户端。
- 灵活性:可以在不影响客户端的情况下修改子系统的内部实现。
适用场景
- 当需要为一个复杂的子系统提供一个简单的接口时。
- 当客户端与子系统之间存在大量依赖关系,且希望减少这种依赖时。
- 当需要将子系统分层时,外观模式可以作为每层的入口点。
例子
- 场景:你需要操作电脑硬件(如 CPU、硬盘、内存),但直接调用这些硬件的接口很复杂。
- 解决方案:使用外观模式,提供一个统一的接口(如操作系统)来简化硬件操作。
- 应用:适用于简化复杂系统调用的场景。
总结
- 外观模式:用于简化复杂系统的接口,提供一个统一的入口点,适用于需要隐藏系统复杂性的场景。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!
享元模式(Flyweight Pattern)
享元模式是一种结构型设计模式,旨在通过共享尽可能多的相似对象来减少内存使用或计算开销。它适用于大量对象且这些对象有相似状态的情况。
组成成分
- 享元接口(Flyweight Interface):定义对象的通用接口,用于操作共享的内部状态。
- 具体享元类(Concrete Flyweight):实现享元接口,并存储内部状态。内部状态是共享的,不会随外部环境变化。
- 享元工厂(Flyweight Factory):创建并管理享元对象,确保共享对象的唯一性。
- 客户端(Client):维护外部状态,并在需要时将外部状态传递给享元对象。定义不需要共享的状态或对象。
特点
- 共享对象:享元模式通过共享相似对象来减少内存占用。
- 分离内部状态和外部状态:内部状态是共享的,外部状态由客户端维护并传递给享元对象。
- 提高性能:通过减少对象数量,享元模式可以显著提高性能,特别是在处理大量对象时。
适用场景
- 当一个应用程序使用了大量相似对象,且这些对象的大部分状态可以外部化时。
- 当对象的大部分状态可以外部化,且可以通过共享来减少内存占用时。
- 当需要缓存或池化对象以提高性能时。
例子
- 场景:你在开发一个游戏,需要创建大量树木对象,但这些对象的状态(如纹理、颜色)大部分是相同的。
- 解决方案:使用享元模式,将树木的共享状态(如纹理)提取出来,减少内存占用。
- 应用:适用于存在大量相似对象的场景,如游戏中的棋子、字符等。
总结
- 享元模式:用于优化大量相似对象的内存使用,通过共享对象来减少资源消耗,适用于需要处理大量相似对象的场景。
关注公众号「编程Cookbook」,获取更多编程学习/面试资料!