设计模式之结构型
一、What
用于处理类或对象的组成结构,即如何将类或对象组合成更大的结构。关注对象的组合、接口的设计以及类的组合关系。可更灵活地设计类结构,提高代码的可复用性和可维护性。
1.1 适配器模式
1.1.1 概念
将一个类的接口转换成客户端所期望的另一个接口,使原本因接口不兼容而无法一起工作的类能够协同工作。适配器模式的核心思想是提供一个中介类(适配器),其将不兼容的接口转换为兼容的接口。这样,客户端就可以通过目标接口与适配器交互,而不需要知道具体的被适配者。
1.1.2 特征
- 接口转换:将一个类的接口转换成客户期望的另一个接口。
- 协同工作:使原本接口不兼容的类可以一起工作。
- 复用功能:通过适配,可以让已有的功能在不修改代码的情况下得到复用。
1.1.3 使用场景
- 系统升级:当需要使用一个新的接口替换一个旧的接口时,可使用适配器模式来保持对旧接口的兼容性。
- 第三方库集成:当使用第三方库时,如果其接口与现有系统的接口不兼容,可以使用适配器模式进行转换。
- 旧系统改造:在维护旧系统时,如果需要添加新功能或替换部分组件,而这些新组件的接口与旧系统不兼容,可以使用适配器模式。
1.1.4 类之间的关系
- 目标接口:定义客户端使用的特定领域接口。
- 适配器:实现目标接口,并包装一个或多个不兼容的类,以使其与客户端一起工作。
- 被适配者:拥有需要被适配的接口,但与目标接口不兼容。
- 客户类:在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
1.1.5 优缺点
1、优点
- 提高类的复用:通过适配器,可以将不兼容的类组合在一起使用。
- 增加类的透明度:用户可以通过目标接口调用适配器,而不需要知道适配器的具体实现。
- 灵活性好:通过适配器,可以灵活地在不修改原有代码的情况下添加新的功能。
2、缺点
- 增加系统复杂性:如果系统中大量使用适配器,可能会使系统变得复杂且难以维护。
- 可能引入性能问题:适配器在转换接口时可能需要进行额外的处理,从而影响性能。
1.1.6 代码示例
#include <iostream>
#include <memory>
// 被适配者类:这个类有一个特定的接口,我们想要适配它
class Adaptee {
public:
void specificRequest() {
std::cout << "Called specificRequest() in Adaptee." << std::endl;
}
};
// 目标接口:这是客户端期望的接口
class Target {
public:
virtual ~Target() = default;
virtual void request() = 0; // 这是客户端想要调用的方法
};
// 适配器类:它将Adaptee的接口转换为Target的接口
class Adapter : public Target {
private:
std::unique_ptr<Adaptee> adaptee; // 持有对被适配者的引用
public:
Adapter(std::unique_ptr<Adaptee> &&adapteePtr)
: adaptee(std::move(adapteePtr)) {}
// 实现Target接口中的方法,通过调用Adaptee的方法来实现
void request() override {
// 在这里可以进行一些额外的处理,或者直接调用Adaptee的方法
adaptee->specificRequest();
std::cout << "Called request() in Adapter, which forwards to Adaptee." << std::endl;
}
};
// 客户端类:它使用Target接口
class Client {
private:
std::unique_ptr<Target> target;
public:
Client(std::unique_ptr<Target> &&targetPtr)
: target(std::move(targetPtr)) {}
void makeRequest() {
target->request(); // 客户端通过Target接口调用方法
}
};
int main() {
// 创建被适配者对象
std::unique_ptr<Adaptee> adaptee = std::make_unique<Adaptee>();
// 创建适配器对象,并将被适配者对象传递给适配器
std::unique_ptr<Target> adapter = std::make_unique<Adapter>(std::move(adaptee));
// 创建客户端对象,并将适配器对象传递给客户端
Client client(std::move(adapter));
// 客户端调用方法,这将通过适配器间接调用被适配者的方法
client.makeRequest();
return 0;
}
1.2 外观模式
1.2.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
1.3 桥接模式
1.1.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
1.4 代理模式
1.1.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
1.5 享元模式
1.1.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
1.6 组合模式
1.1.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
1.7 装配器模式
1.1.1 概念
1.1.2 特征
1.1.3 使用场景
1.1.4 类之间的关系
1.1.5 优缺点
1.1.6 代码示例
二、Why
解决软件开发中类或对象结构设计的复杂性和灵活性问题。通过应用这些模式,开发者可以更加清晰地组织代码,提高代码的可读性和可维护性;这些模式还提供了灵活的类结构,使得系统可以更容易地适应变化。
三、Who
具有丰富软件开发经验的架构师或设计师来应用。根据系统的需求和目标,选择合适的结构型模式,并对其进行定制和扩展,以满足系统的特定要求。
四、When
结构型模式在以下情况下使用最为合适:
- 当需要设计复杂的类结构时,可以使用结构型模式来简化设计过程。
- 当需要提高代码的可复用性和可维护性时,可以通过应用结构型模式来实现。
- 当系统需要适应变化时,结构型模式提供了灵活的类结构,使得系统可以更容易地进行调整和优化。
五、Where
各种软件开发场景,特别是在面向对象编程(OOP)环境中。它们可用于设计系统的各个层次,包括类库、框架和应用程序等。通过应用结构型模式,开发者可以更加灵活地组织代码,提高系统的可扩展性和可维护性。
六、How
开发者需遵循以下步骤:
- 识别问题:识别系统中存在的问题,问题可能与类结构的复杂性、灵活性或可维护性有关。
- 选择模式:根据问题的性质和目标,选择合适的结构型模式。
- 设计实现:在应用所选模式时,需要进行详细的设计和实现工作。包括定义接口、实现类、创建对象等。
- 测试验证:在设计和实现完成后,需要对系统进行测试验证,以确保所选模式能够有效地解决存在的问题,并满足系统的需求。
七、How Much
虽然结构型模式可提高代码的可复用性和可维护性,但也会增加系统的复杂性和开发成本。因此,开发者需要在权衡利弊后做出决策,以确定是否应用这些模式以及应用多少。
具体来说,这包括评估所需的工作量、开发时间、资源成本等,并与预期的收益进行比较。如果成本过高而收益有限,那么可能需要考虑其他解决方案或简化模式的应用。