C++设计模式(更新中)
文章目录
- 1、创建型模式
- 1.1 简单工厂(Simple Factory)
- (1)示例
- (2)总结
- 1.2 工厂方法(Factory Method)
- (1)示例
- (2)总结
- 1.3 抽象工厂(Abstract Factory)
- (1)示例
- (2)总结
- 1.4 建造者(Builder)
- (1)示例
- (2)总结
1、创建型模式
1.1 简单工厂(Simple Factory)
简单工厂模式通过一个工厂类负责对象的创建,客户端只需提供参数,工厂类根据条件返回相应的对象。这种方式可以减少客户端对具体实现的依赖。
核心结构
- 工厂类:负责根据传入的参数生成具体对象。
- 产品抽象基类:定义所有产品的公共接口。
- 产品类:具体的实现类,继承自产品基类。
- 客户端:通过工厂类创建产品对象并调用其方法。
(1)示例
假设正在开发一个工具库,工具种类包含绘制工具 (DrawTool) 和擦除工具 (EraseTool)。为了简化工具对象的创建和使用,可以通过简单工厂模式来生成这些工具。
库代码实现
-
工具抽象类和具体工具类
Tool.h
:为了方便,这里没有分文件编写,实际中也可以分成Tool.h
+Tool.cpp
,工具太多也可以每个具体工具和工具接口类全部分开编写。之后的示例代码也是一样。#pragma once #include <iostream> // 工具基类 class Tool { public: virtual void UseTool() = 0; virtual ~Tool() = default; }; // 具体工具类1 - 绘制工具 class DrawTool : public Tool { public: void UseTool() override { std::cout << "Using Draw Tool!" << std::endl; } }; // 具体工具类2 - 擦除工具 class EraseTool : public Tool { public: void UseTool() override { std::cout << "Using Erase Tool!" << std::endl; } };
-
工厂类 (
ToolFactory.h
和ToolFactory.cpp
)
ToolFactory.h
:#pragma once #include <memory> #include "Tool.h" // 工厂类,用于创建工具对象 class ToolFactory { public: enum class ToolType { Draw, Erase }; // 简单工厂创建工具的静态方法 static std::unique_ptr<Tool> CreateTool(ToolType type); };
ToolFactory.cpp
:#include "ToolFactory.h" std::unique_ptr<Tool> ToolFactory::CreateTool(ToolType type) { switch (type) { case ToolType::Draw: return std::make_unique<DrawTool>(); case ToolType::Erase: return std::make_unique<EraseTool>(); default: return nullptr; } }
使用者代码
使用者只需要引入 ToolFactory.h
,通过工厂类创建工具对象,而不需要知道具体的工具类 DrawTool
或 EraseTool
。这减少了客户端和库中具体实现的依赖关系。
从下面代码,可以看到,实际上使用者这边还是需要依赖于工具基类的信息的(没有包含Tool.h
是因为工厂类已经包含了),但是不需要知道具体的工具类的信息
Main.cpp
:
#include "ToolFactory.h"
int main()
{
// 使用工厂创建绘制工具
std::unique_ptr<Tool> tool = ToolFactory::CreateTool(ToolFactory::ToolType::Draw);
if (tool)
tool->UseTool();
// 使用工厂创建擦除工具
tool = ToolFactory::CreateTool(ToolFactory::ToolType::Erase);
if (tool)
tool->UseTool();
return 0;
}
(2)总结
-
优点:
- 减少依赖:客户端只需依赖工厂类与产品基类,而不需要依赖具体产品类。
- 解耦创建与使用:工厂类封装了创建细节,客户端只负责使用产品。
-
缺点:
- 违反开闭原则:每次添加新的工具类型,必须修改工厂类的
CreateTool
方法,添加一条新的case:
,导致不符合 “对修改关闭,对扩展开放”的原则。在扩展性和代码维护上有一定的缺点,特别是在需要频繁添加新工具类型的情况下。 - 工厂类复杂化:工厂类会随产品种类的增加而变得复杂。
- 违反开闭原则:每次添加新的工具类型,必须修改工厂类的
1.2 工厂方法(Factory Method)
工厂方法模式通过将创建对象的逻辑推迟到子类中,从而解决简单工厂模式违反开闭原则的问题。它的核心理念是将工厂类拆分成多个具体工厂,每个工厂类负责创建某种特定产品。
核心结构
抽象产品 + 具体产品,抽象工厂 + 具体工厂
(1)示例
重构1.1中的示例代码,工具基类及其子类保持不变,但工厂类需要改造成每种产品对应一个工厂子类,各自负责产品的创建。
- 工厂类
ToolFactory.h
#pragma once #include <memory> #include "Tool.h" class ToolFactory { public: virtual std::unique_ptr<Tool> CreateTool() = 0; virtual ~ToolFactory() = default; }; class DrawToolFactory : public ToolFactory { public: std::unique_ptr<Tool> CreateTool() override { return std::make_unique<DrawTool>(); } }; class EraseToolFactory : public ToolFactory { public: std::unique_ptr<Tool> CreateTool() override { return std::make_unique<EraseTool>(); } };
- 客户端使用
main.cpp
#include "ToolFactory.h" int main() { // 创建绘制工具工厂对象 std::unique_ptr<ToolFactory> drawFactory = std::make_unique<DrawToolFactory>(); // 创建绘制工具 std::unique_ptr<Tool> drawTool = drawFactory->CreateTool(); if (drawTool) drawTool->UseTool(); // 创建擦除工具工厂对象 std::unique_ptr<ToolFactory> eraseFactory = std::make_unique<EraseToolFactory>(); // 创建擦除工具 std::unique_ptr<Tool> eraseTool = eraseFactory->CreateTool(); if (eraseTool) eraseTool->UseTool(); return 0; }
(2)总结
-
优点
- 符合开闭原则:新增产品时,只需新增对应的工厂类,而无需修改已有代码。
- 单一职责:每个工厂类只负责创建一种产品,职责更为明确。
- 可扩展性:更方便地增加新产品和新工厂,且代码修改局部化,减少潜在错误。
-
缺点
- 类的增加:每种产品都需要对应的工厂类,会导致类的数量增加,增加系统的复杂性。
- 复杂度提升:对于简单的对象创建场景,工厂方法模式可能显得过于复杂,因为每个具体产品都需要单独的工厂类。
- 职责分散:每个工厂类只负责创建某种具体产品,可能导致系统中存在较多分散的创建逻辑,维护起来可能不便。
1.3 抽象工厂(Abstract Factory)
在某些情况下,我们需要创建一组相关或依赖的对象,而不仅仅是单个对象。工厂方法模式虽然可以创建单个产品,但如果需要创建多个产品,并且这些产品之间存在某种强关联(比如 UI 系统中不同平台的按钮、文本框等,比如windows平台的按钮必须搭配同平台的文本框),工厂方法模式就显得不足了。
抽象工厂模式提供一个接口,用于创建相关联的对象族,而不指定它们的具体类。它解决了创建多类相关产品的问题,而不是单个产品。
核心结构:
- 抽象工厂类:定义创建一系列相关产品的接口。
- 具体工厂类:实现抽象工厂的接口,负责创建一系列具体产品。
- 抽象产品类:为不同产品提供统一的接口。
- 具体产品类:每个具体工厂创建的不同产品实现类。
- 客户端:通过抽象工厂接口来获取相关联的产品。
(1)示例
假设现在有4种具体工具产品,分别是2D绘制工具、2D擦除工具、3D绘制工具、3D擦除工具,正常情况来说,2D绘制工具跟2D擦除工具肯定是配套的,如果工厂方法来创建,就可能混淆使用,而抽象工厂则可以避免这种错误产生
-
Tool.h
:这里从Tool
接口类直接派生了4个工具,也可以这样做(根据需求灵活处理):- 定义
2DTool
抽象类和3DTool
抽象类,然后再分别派生2个具体工具类出来 - 定义
DrawTool
抽象类和EraseTool
抽象类,然后再分别派生2个具体工具类出来
#pragma once #include <iostream> // 抽象产品类 - 工具 class Tool { public: virtual void UseTool() = 0; virtual ~Tool() = default; }; // 具体产品类 - 2D绘制工具 class Draw2DTool : public Tool { public: void UseTool() override { std::cout << "Using 2D Draw Tool!" << std::endl; } }; // 具体产品类 - 2D擦除工具 class Erase2DTool : public Tool { public: void UseTool() override { std::cout << "Using 2D Erase Tool!" << std::endl; } }; // 具体产品类 - 3D绘制工具 class Draw3DTool : public Tool { public: void UseTool() override { std::cout << "Using 3D Draw Tool!" << std::endl; } }; // 具体产品类 - 3D擦除工具 class Erase3DTool : public Tool { public: void UseTool() override { std::cout << "Using 3D Erase Tool!" << std::endl; } };
- 定义
-
Factory.h
#pragma once #include <memory> #include "Tool.h" // 抽象工厂类 class ToolFactory { public: virtual std::unique_ptr<Tool> CreateDrawTool() = 0; virtual std::unique_ptr<Tool> CreateEraseTool() = 0; virtual ~ToolFactory() = default; }; // 具体工厂类 - 2D工具工厂 class Tool2DFactory : public ToolFactory { public: std::unique_ptr<Tool> CreateDrawTool() override { return std::make_unique<Draw2DTool>(); } std::unique_ptr<Tool> CreateEraseTool() override { return std::make_unique<Erase2DTool>(); } }; // 具体工厂类 - 3D工具工厂 class Tool3DFactory : public ToolFactory { public: std::unique_ptr<Tool> CreateDrawTool() override { return std::make_unique<Draw3DTool>(); } std::unique_ptr<Tool> CreateEraseTool() override { return std::make_unique<Erase3DTool>(); } };
-
main.cpp
// Main.cpp #include "Factory.h" int main() { // 创建2D工厂 std::unique_ptr<ToolFactory> factory2D = std::make_unique<Tool2DFactory>(); std::unique_ptr<Tool> tool = factory2D->CreateDrawTool(); tool->UseTool(); tool = factory2D->CreateEraseTool(); tool->UseTool(); // 创建3D工厂 std::unique_ptr<ToolFactory> factory3D = std::make_unique<Tool3DFactory>(); tool = factory3D->CreateDrawTool(); tool->UseTool(); tool = factory3D->CreateEraseTool(); tool->UseTool(); return 0; }
(2)总结
-
优点
- 产品族一致性:通过具体工厂类,确保了 2D 和 3D 工具的内部一致性,避免在客户端中创建不兼容的工具(如混用 2D 和 3D 工具)。
- 在新增工具方面,遵守开闭原则:新增其他绘制工具类型时,比如一个4D工具,只需额外实现新的具体工厂类和对应的产品类,保证了系统的可扩展性。
- 减少客户端依赖:客户端只依赖抽象工厂和抽象产品,而不需要关心具体实现的细节,符合依赖倒置原则。
-
缺点
- 复杂性:增加了工厂类和产品类的数量,系统结构更加复杂,尤其当产品族多时会造成一定的维护压力。
- 在新增每种工具的功能时,不遵守开闭原则:比如现在的每种工具套件都支持绘制、擦除工具,我还想新增一个修改工具,则需要修改每一个工厂类和每一个产品类的代码。
- 灵活性降低:所有工厂创建的产品都是预定义的,无法灵活组合不同种类的产品。
1.4 建造者(Builder)
建造者模式的作用是将复杂对象的组装过程和对象的实际表示区分开来。这样,相同的组装步骤可以用来制造出不同的对象表现形式。这种模式非常适合于那些由多个可选组件组成的复杂对象。通过分步骤地构建每个组件,我们可以根据需要灵活地组合出各种不同的对象。
建造者模式的核心理念是通过引入一个建造者(Builder)
类,负责构建对象的各个部分,而不是在一个复杂的构造函数中完成所有工作。它能够分阶段创建对象,允许客户端在构建过程中灵活选择和配置对象的组件。该模式还允许通过实现不同的具体建造者,来生成不同类型或版本的产品对象。
直接上具体案例,来理解这个模式
(1)示例
组装电脑,电脑有很多可定制的部件(如CPU、GPU、硬盘、内存等),对于不同的需求(比如游戏电脑、办公电脑),需要选用不同的性能、品牌的部件,建造者模式可以灵活地应对这些变化。
类之间的关系
Director
:通过Builder
来控制建造流程,确保每个步骤按顺序调用。它不需要知道具体的产品细节,只需要负责调用Builder
提供的接口。Builder
:定义了构建的接口,具体的建造步骤(如BuildCPU()
、BuildGPU()
等)。ConcreteBuilder
:实现Builder
接口的具体类,负责创建不同类型的产品,如游戏电脑和办公电脑。每个ConcreteBuilder
实现了相同的建造步骤,但生成不同的结果。Product
:是最终生成的对象,它包含了多个部分(CPU、GPU、Memory 等),由ConcreteBuilder
一步步构建。
#include <iostream>
#include <string>
// 产品类:电脑
class Computer
{
public:
void SetCPU(const std::string& cpu) { CPU = cpu; }
void SetGPU(const std::string& gpu) { GPU = gpu; }
void SetMemory(const std::string& memory) { Memory = memory; }
void SetStorage(const std::string& storage) { Storage = storage; }
void ShowSpecifications() const
{
std::cout << "Computer Specifications:\n";
std::cout << "CPU: " << CPU << "\n";
std::cout << "GPU: " << GPU << "\n";
std::cout << "Memory: " << Memory << "\n";
std::cout << "Storage: " << Storage << "\n";
}
private:
std::string CPU;
std::string GPU;
std::string Memory;
std::string Storage;
};
// 抽象建造者:定义创建产品的步骤
class ComputerBuilder
{
public:
virtual ~ComputerBuilder() = default;
virtual void BuildCPU() = 0;
virtual void BuildGPU() = 0;
virtual void BuildMemory() = 0;
virtual void BuildStorage() = 0;
virtual Computer* GetResult() = 0;
};
// 具体建造者:游戏电脑
class GamingComputerBuilder : public ComputerBuilder
{
private:
Computer* computer;
public:
GamingComputerBuilder()
{
computer = new Computer();
}
~GamingComputerBuilder()
{
delete computer;
}
void BuildCPU() override
{
computer->SetCPU("i9");
}
void BuildGPU() override
{
computer->SetGPU("RTX4090 ");
}
void BuildMemory() override
{
computer->SetMemory("16GB DDR4");
}
void BuildStorage() override
{
computer->SetStorage("1TB SSD");
}
Computer* GetResult() override
{
return computer;
}
};
// 具体建造者:办公电脑
class OfficeComputerBuilder : public ComputerBuilder
{
private:
Computer* computer;
public:
OfficeComputerBuilder()
{
computer = new Computer();
}
~OfficeComputerBuilder()
{
delete computer;
}
void BuildCPU() override
{
computer->SetCPU("i3");
}
void BuildGPU() override
{
computer->SetGPU("GTX 960");
}
void BuildMemory() override
{
computer->SetMemory("8GB DDR4");
}
void BuildStorage() override
{
computer->SetStorage("512GB SSD");
}
Computer* GetResult() override
{
return computer;
}
};
// 指挥者:控制建造流程
class Director
{
private:
ComputerBuilder* builder = nullptr;
public:
void SetBuilder(ComputerBuilder* builder)
{
this->builder = builder;
}
void Construct()
{
builder->BuildCPU();
builder->BuildGPU();
builder->BuildMemory();
builder->BuildStorage();
}
};
// 客户端代码
int main()
{
Director director;
// 创建游戏电脑
GamingComputerBuilder gamingBuilder;
director.SetBuilder(&gamingBuilder);
director.Construct();
Computer* gamingComputer = gamingBuilder.GetResult();
gamingComputer->ShowSpecifications();
// 创建办公电脑
OfficeComputerBuilder officeBuilder;
director.SetBuilder(&officeBuilder);
director.Construct();
Computer* officeComputer = officeBuilder.GetResult();
officeComputer->ShowSpecifications();
delete gamingComputer;
delete officeComputer;
return 0;
}
(2)总结
-
优点:
-
灵活应对不同需求:在这个例子中,游戏电脑和办公电脑虽然都是由CPU、GPU、内存和硬盘等部件组成,但具体的配置差别很大。通过
Director
控制构建顺序,我们可以复用建造流程,但根据不同的 具体ConcreteBuilder
实现来生成不同的产品。这使得我们在不修改整体构建逻辑的情况下,可以灵活地创建多种不同的对象。 -
易扩展:建造者模式将复杂对象的构建过程与具体的构建实现解耦。
Director
只需要知道如何控制创建流程,具体怎么创建、用什么配置并不重要。通过这种解耦,可以很容易地替换或扩展新的ConcreteBuilder
,比如还可以再实现一个WorkstationComputerBuilder
,用于构建工作站电脑。 -
代码更加清晰:对于复杂对象的构建,通过一步步调用建造方法(如
BuildCPU()
、BuildMemory()
),构建过程非常清晰易懂。这种模式避免了构建过程中混乱的条件判断和冗长的构造代码。
-
-
缺点:
- 增加代码复杂度:模式引入了额外的建造者、指导者等类,可能会使代码量增多,结构变复杂。
- 多次构建成本高:如果每次构建都需要重建所有对象部分,可能导致性能开销。