设计模式实战:从创建型到结构型的全面解析
前言
设计模式是软件开发中解决常见问题的经典方案,掌握设计模式不仅能提升代码质量,还能让我们与团队更好地沟通设计思想。本文将从创建型模式 和结构型模式两大类别出发,结合实际C++代码,详解10个最常用的设计模式。
工厂模式、建造者模式、原型模式、桥接模式、适配器模式、装饰模式、组合模式、享元模式、外观模式、代理模式
一、创建型模式
创建型模式关注对象创建机制,使系统在创建对象时更加灵活、解耦。
1. 工厂模式
工厂模式的核心思想是将对象的创建和使用分离,由专门的"工厂"负责创建对象。
工厂方法
客户端
工厂接口
具体工厂A
具体工厂B
产品A
产品B
简单工厂
客户端
简单工厂
产品A
产品B
三种工厂模式对比
| 模式 | 核心思想 | 何时使用 |
|---|---|---|
| 简单工厂 | 一个工厂根据参数创建不同产品 | 产品种类少,不想为每个类建工厂 |
| 工厂方法 | 每个产品对应一个工厂,工厂也抽象化 | 产品会不断扩展,需要符合开闭原则 |
| 抽象工厂 | 创建一组相关或依赖的对象(产品族) | 需要切换整套产品系列 |
代码示例:工厂方法
cpp
// 抽象产品
class Car {
public:
virtual void run() = 0;
virtual ~Car() = default;
};
// 具体产品
class Bmw : public Car {
public:
void run() override { std::cout << "宝马在飞驰" << std::endl; }
};
// 抽象工厂
class CarFactory {
public:
virtual std::unique_ptr<Car> createCar() = 0;
virtual ~CarFactory() = default;
};
// 具体工厂
class BmwFactory : public CarFactory {
public:
std::unique_ptr<Car> createCar() override {
return std::make_unique<Bmw>();
}
};
// 使用
int main() {
std::unique_ptr<CarFactory> factory = std::make_unique<BmwFactory>();
auto car = factory->createCar();
car->run(); // 输出:宝马在飞驰
}
2. 建造者模式
建造者模式将复杂对象的构建与表示分离,使同样的构建过程可以创建不同的表示。
Director
+construct()
<<interface>>
Builder
+buildPartA()
+buildPartB()
+getResult()
ConcreteBuilder
+buildPartA()
+buildPartB()
+getResult()
Product
+setPart()
经典实现 vs 链式调用
| 对比维度 | 经典模式 | 链式调用 |
|---|---|---|
| 角色完整性 | 完整(Director+Builder+Product) | 简化(Builder+Product) |
| 构建流程控制 | Director统一控制 | 客户端直接控制 |
| 构建顺序复用 | Director可复用 | 需要客户端重新写调用链 |
代码示例:链式调用
cpp
class Pizza {
public:
class Builder {
Pizza pizza_;
public:
Builder& setSize(const std::string& size) {
pizza_.size_ = size;
return *this;
}
Builder& addCheese() {
pizza_.cheese_ = true;
return *this;
}
Pizza build() { return pizza_; }
};
private:
std::string size_;
bool cheese_ = false;
};
// 使用
Pizza pizza = Pizza::Builder()
.setSize("大份")
.addCheese()
.build();
3. 原型模式
原型模式通过复制现有对象来创建新对象,避免昂贵的初始化操作。
clone
clone
clone
原型对象
新对象1
新对象2
新对象3
深拷贝 vs 浅拷贝
| 拷贝方式 | 说明 | 适用场景 |
|---|---|---|
| 浅拷贝 | 只复制对象本身,引用成员共享 | 成员都是值类型 |
| 深拷贝 | 递归复制所有引用成员 | 对象包含动态资源 |
代码示例
cpp
// 抽象原型
class Shape {
public:
virtual std::unique_ptr<Shape> clone() const = 0;
virtual void draw() const = 0;
virtual ~Shape() = default;
};
// 具体原型
class Circle : public Shape {
private:
int radius;
std::string color;
public:
Circle(int r, const std::string& c) : radius(r), color(c) {}
// 拷贝构造函数(深拷贝)
Circle(const Circle& other) = default;
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Circle>(*this);
}
void draw() const override {
std::cout << "Circle: radius=" << radius << ", color=" << color << std::endl;
}
};
// 使用
int main() {
auto original = std::make_unique<Circle>(10, "red");
auto cloned = original->clone(); // 多态克隆
cloned->draw();
}
二、结构型模式
结构型模式关注如何将类和对象组合成更大的结构。
4. 桥接模式
桥接模式将抽象部分与实现部分分离,使它们可以独立变化。
Shape
#color: Color
+draw()
<<interface>>
Color
+applyColor()
Circle
+draw()
Red
+applyColor()
Blue
+applyColor()
解决什么问题?
问题:有M种形状和N种颜色,使用继承需要创建 M×N 个类。
解决:桥接模式只需 M + N 个类。
代码示例
cpp
// 实现部分:颜色接口
class Color {
public:
virtual void applyColor() const = 0;
};
class Red : public Color {
public:
void applyColor() const override {
std::cout << "应用红色" << std::endl;
}
};
// 抽象部分:形状
class Shape {
protected:
std::shared_ptr<Color> color_;
public:
Shape(std::shared_ptr<Color> color) : color_(color) {}
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
Circle(std::shared_ptr<Color> color) : Shape(color) {}
void draw() const override {
std::cout << "绘制圆形,";
color_->applyColor();
}
};
// 使用
int main() {
auto red = std::make_shared<Red>();
auto circle = Circle(red);
circle.draw(); // 绘制圆形,应用红色
}
5. 适配器模式
适配器模式让不兼容的接口能够一起工作。
调用
实现
转换调用
客户端
目标接口
适配器
适配者
类适配器 vs 对象适配器
| 对比维度 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 多重继承 | 对象组合 |
| 耦合度 | 高(编译时绑定) | 低(运行时绑定) |
| 灵活性 | 低 | 高(可动态切换) |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
代码示例:对象适配器
cpp
// 目标接口(客户端期望)
class ChineseSocket {
public:
virtual void charge_220v() const {
std::cout << "使用220V电压充电" << std::endl;
}
};
// 适配者(需要适配的类)
class USASocket {
public:
void charge_110v() const {
std::cout << "使用110V电压充电" << std::endl;
}
};
// 适配器
class USAAdapter : public ChineseSocket {
private:
std::unique_ptr<USASocket> usa_socket_;
public:
USAAdapter(std::unique_ptr<USASocket> socket)
: usa_socket_(std::move(socket)) {}
void charge_220v() const override {
std::cout << "[适配器] 将110V转换为220V: ";
usa_socket_->charge_110v();
}
};
// 使用
int main() {
auto usaSocket = std::make_unique<USASocket>();
USAAdapter adapter(std::move(usaSocket));
adapter.charge_220v(); // 美国插座也能在中国使用
}
6. 装饰模式
装饰模式动态地给对象添加新功能,比继承更灵活。
核心组件
装饰器1
装饰器2
装饰器3
装饰模式 vs 继承
装饰模式
核心
装饰器A
装饰器B
装饰器C
继承方式
父类
子类1
子类2
孙类1-1
孙类1-2
代码示例:咖啡订单系统
cpp
// 抽象组件
class Beverage {
public:
virtual std::string getDescription() const = 0;
virtual double cost() const = 0;
virtual ~Beverage() = default;
};
// 具体组件
class Espresso : public Beverage {
public:
std::string getDescription() const override { return "浓缩咖啡"; }
double cost() const override { return 25.0; }
};
// 抽象装饰类
class CondimentDecorator : public Beverage {
protected:
std::unique_ptr<Beverage> beverage_;
public:
CondimentDecorator(std::unique_ptr<Beverage> bev)
: beverage_(std::move(bev)) {}
};
// 具体装饰:牛奶
class Milk : public CondimentDecorator {
public:
Milk(std::unique_ptr<Beverage> bev)
: CondimentDecorator(std::move(bev)) {}
std::string getDescription() const override {
return beverage_->getDescription() + " + 牛奶";
}
double cost() const override { return beverage_->cost() + 5.0; }
};
// 使用
int main() {
auto coffee = std::make_unique<Espresso>();
coffee = std::make_unique<Milk>(std::move(coffee));
coffee = std::make_unique<Milk>(std::move(coffee)); // 双份牛奶
std::cout << coffee->getDescription() << ": ¥" << coffee->cost() << std::endl;
// 输出:浓缩咖啡 + 牛奶 + 牛奶: ¥35
}
7. 组合模式
组合模式将对象组合成树形结构 ,使客户端对单个对象和组合对象的使用具有一致性。
根节点 Composite
叶子 Leaf
子节点 Composite
叶子 Leaf
叶子 Leaf
代码示例:公司组织架构
cpp
// 抽象组件
class Employee {
public:
virtual void showInfo(int depth = 0) = 0;
virtual void add(std::shared_ptr<Employee>) {}
virtual ~Employee() = default;
};
// 叶子:普通员工
class OrdinaryEmployee : public Employee {
private:
std::string name_;
std::string position_;
public:
OrdinaryEmployee(std::string name, std::string pos)
: name_(name), position_(pos) {}
void showInfo(int depth = 0) override {
std::string indent(depth * 2, ' ');
std::cout << indent << "- " << name_ << " (" << position_ << ")" << std::endl;
}
};
// 组合:经理
class Manager : public Employee {
private:
std::string name_;
std::string position_;
std::vector<std::shared_ptr<Employee>> subordinates_;
public:
Manager(std::string name, std::string pos)
: name_(name), position_(pos) {}
void add(std::shared_ptr<Employee> emp) override {
subordinates_.push_back(emp);
}
void showInfo(int depth = 0) override {
std::string indent(depth * 2, ' ');
std::cout << indent << "+ " << name_ << " (经理 - " << position_ << ")" << std::endl;
for (auto& emp : subordinates_) {
emp->showInfo(depth + 1);
}
}
};
// 使用
int main() {
auto ceo = std::make_shared<Manager>("张三", "CEO");
auto techManager = std::make_shared<Manager>("李四", "技术总监");
auto dev = std::make_shared<OrdinaryEmployee>("王五", "工程师");
ceo->add(techManager);
techManager->add(dev);
ceo->showInfo();
}
8. 享元模式
享元模式通过共享大量细粒度对象来减少内存占用。
享元模式
共享对象A
引用1
引用2
共享对象B
引用3
传统方式
对象1
对象2
对象3
对象4
内在状态 vs 外在状态
| 状态类型 | 说明 | 示例 |
|---|---|---|
| 内在状态 | 可共享,存储在享元对象内部 | 字符的ASCII码 |
| 外在状态 | 不可共享,由客户端传递 | 字体大小、颜色 |
代码示例:字符共享
cpp
// 享元接口
class Character {
public:
virtual void display(int fontSize, const std::string& color) = 0;
virtual ~Character() = default;
};
// 具体享元
class ConcreteCharacter : public Character {
private:
char symbol_; // 内在状态(可共享)
public:
ConcreteCharacter(char c) : symbol_(c) {}
void display(int fontSize, const std::string& color) override {
std::cout << "字符: " << symbol_
<< ", 字号: " << fontSize
<< ", 颜色: " << color << std::endl;
}
};
// 享元工厂
class CharacterFactory {
private:
std::unordered_map<char, std::shared_ptr<Character>> pool_;
public:
std::shared_ptr<Character> getCharacter(char key) {
if (pool_.find(key) == pool_.end()) {
pool_[key] = std::make_shared<ConcreteCharacter>(key);
std::cout << "创建新字符: " << key << std::endl;
}
return pool_[key];
}
size_t size() const { return pool_.size(); }
};
// 使用
int main() {
CharacterFactory factory;
std::string text = "Hello";
for (char c : text) {
auto ch = factory.getCharacter(c);
ch->display(12, "Black");
}
// 输出:只创建了4个字符对象(H,e,l,o),l被重用
std::cout << "实际创建对象数: " << factory.size() << std::endl;
}
9. 外观模式
外观模式为子系统提供统一的简化接口。
SubC SubB SubA Facade Client SubC SubB SubA Facade Client operation() doA() doB() doC() 完成
代码示例:家庭影院
cpp
// 子系统类
class Projector {
public:
void on() { std::cout << "投影仪打开" << std::endl; }
void off() { std::cout << "投影仪关闭" << std::endl; }
};
class SoundSystem {
public:
void on() { std::cout << "音响打开" << std::endl; }
void setVolume(int level) { std::cout << "音量: " << level << std::endl; }
};
class BluRayPlayer {
public:
void on() { std::cout << "播放器打开" << std::endl; }
void play(const std::string& movie) { std::cout << "播放: " << movie << std::endl; }
};
// 外观类
class HomeTheaterFacade {
private:
Projector* projector_;
SoundSystem* sound_;
BluRayPlayer* player_;
public:
HomeTheaterFacade(Projector* p, SoundSystem* s, BluRayPlayer* b)
: projector_(p), sound_(s), player_(b) {}
void watchMovie(const std::string& movie) {
std::cout << "=== 开始观影 ===" << std::endl;
projector_->on();
sound_->on();
sound_->setVolume(30);
player_->on();
player_->play(movie);
std::cout << "=== 观影中 ===" << std::endl;
}
void endMovie() {
std::cout << "=== 结束观影 ===" << std::endl;
projector_->off();
sound_->off();
player_->off();
}
};
// 使用
int main() {
Projector proj;
SoundSystem sound;
BluRayPlayer player;
HomeTheaterFacade theater(&proj, &sound, &player);
theater.watchMovie("盗梦空间");
theater.endMovie();
}
10. 代理模式
代理模式为另一个对象提供替身以控制访问。
<<interface>>
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()
代理模式分类
| 类型 | 用途 | 示例 |
|---|---|---|
| 虚拟代理 | 延迟加载 | 图片懒加载 |
| 保护代理 | 访问控制 | 权限检查 |
| 远程代理 | 隐藏地址空间 | RPC调用 |
| 智能引用 | 附加操作 | 日志、缓存 |
代码示例:保护代理
cpp
// 抽象主题
class Document {
public:
virtual void read() = 0;
virtual void write(const std::string& content) = 0;
virtual ~Document() = default;
};
// 真实主题
class RealDocument : public Document {
private:
std::string content_;
public:
void read() override { std::cout << "阅读: " << content_ << std::endl; }
void write(const std::string& c) override { content_ = c; }
};
// 代理:带权限控制
class DocumentProxy : public Document {
private:
std::unique_ptr<RealDocument> realDoc_;
std::string userRole_;
bool hasWritePermission() const {
return userRole_ == "admin" || userRole_ == "editor";
}
public:
DocumentProxy(const std::string& role)
: realDoc_(std::make_unique<RealDocument>()), userRole_(role) {}
void read() override {
realDoc_->read(); // 所有人都可读
}
void write(const std::string& content) override {
if (hasWritePermission()) {
realDoc_->write(content);
std::cout << "写入成功" << std::endl;
} else {
std::cout << "权限不足,无法写入" << std::endl;
}
}
};
// 使用
int main() {
auto userDoc = DocumentProxy("user");
userDoc.write("新内容"); // 失败
auto adminDoc = DocumentProxy("admin");
adminDoc.write("重要内容"); // 成功
}
三、模式对比总结
创建型模式对比
| 模式 | 核心问题 | 解决方案 |
|---|---|---|
| 工厂方法 | 创建单个对象 | 将创建延迟到子类 |
| 抽象工厂 | 创建产品族 | 用工厂创建相关对象 |
| 建造者 | 创建复杂对象 | 分步构建 |
| 原型模式 | 创建相似对象 | 克隆现有对象 |
结构型模式对比
| 模式 | 核心思想 | 关键特征 |
|---|---|---|
| 桥接 | 分离抽象与实现 | 两个独立维度变化 |
| 适配器 | 接口转换 | 让不兼容接口协作 |
| 装饰 | 动态添加功能 | 比继承更灵活 |
| 组合 | 树形结构 | 统一对待叶子与容器 |
| 享元 | 共享对象 | 减少内存占用 |
| 外观 | 简化接口 | 封装复杂子系统 |
| 代理 | 控制访问 | 延迟加载/权限控制 |
四、选择建议
是
是
否
是
否
是
否
否
是
否
是
否
是
否
是
否
是
否
是
否
遇到设计问题
需要创建对象?
对象创建复杂?
建造者模式
需要克隆?
原型模式
产品会扩展?
工厂方法
简单工厂
需要接口转换?
适配器模式
需要控制访问?
代理模式
需要减少内存?
享元模式
有树形结构?
组合模式
需要简化接口?
外观模式
多维度变化?
桥接模式
装饰模式
核心原则
别一开始就上最复杂的设计。先用简单方案解决问题,当发现需要扩展时,再平滑地重构为更合适的设计模式。
五、设计模式应用场景速查表
1.创建型模式
| 模式 | 核心问题 | 典型应用场景 | 何时使用 | 何时避免 |
|---|---|---|---|---|
| 简单工厂 | 根据参数创建不同产品 | 解析配置文件创建对象、日志记录器创建、数据库驱动加载 | 产品种类少(<5种),调用者只需传参,不想关心创建细节 | 产品频繁增加,需要经常修改工厂类 |
| 工厂方法 | 创建单个对象,支持扩展 | 跨平台UI组件创建、文档编辑器支持多种格式、游戏角色生成器 | 产品会不断扩展,需要符合开闭原则,每个产品有专属创建逻辑 | 产品类型固定不变,或创建逻辑极其简单 |
| 抽象工厂 | 创建一系列相关对象(产品族) | 跨平台UI工具包(Windows/Mac/Linux风格)、数据库连接族(连接/命令/事务)、游戏皮肤系统 | 需要切换整套产品系列,需要保证产品族内对象兼容性 | 产品族不会变化,或产品间没有关联关系 |
| 建造者 | 分步创建复杂对象 | SQL查询构建器、HTTP请求构建器、PDF文档生成器、披萨订单系统 | 构造参数多且部分可选(>5个),需要控制构建顺序,需要生成不同表示形式 | 对象构造简单(参数<3个),或不需要多种表示形式 |
| 原型模式 | 通过克隆创建对象 | 游戏中的怪物复制、图形编辑器的复制粘贴、单元格样式克隆、大对象缓存 | 对象创建成本高(复杂计算/数据库查询),需要运行时动态配置对象类型 | 对象有循环引用,或克隆实现极其复杂 |
2.结构型模式
| 模式 | 核心问题 | 典型应用场景 | 何时使用 | 何时避免 |
|---|---|---|---|---|
| 适配器 | 让不兼容接口协作 | 不同日志库统一接口、新旧系统API对接、第三方支付集成、电源适配器 | 需要集成第三方库但接口不匹配,需要兼容旧版API,需要统一多个相似接口 | 可以修改原始类接口,或系统设计阶段就能统一接口 |
| 桥接 | 分离抽象与实现 | 跨平台绘图(形状+渲染引擎)、消息系统(消息类型+发送方式)、遥控器(设备+控制) | 有两个以上独立变化的维度,需要避免M×N的类爆炸,需要在运行时切换实现 | 只有一个变化维度,或维度间没有独立变化的需求 |
| 组合 | 树形结构统一处理 | 文件系统(目录+文件)、组织架构(部门+员工)、GUI控件(容器+控件)、菜单系统 | 有部分-整体的层次结构,需要统一对待叶子节点和容器节点 | 结构是扁平的,或不需要递归操作 |
| 装饰 | 动态添加功能 | 咖啡配料系统、数据流加密/压缩、GUI控件添加边框/滚动条、权限动态增强 | 需要在不修改原类的情况下动态添加功能,功能组合数量大(避免类爆炸),需要运行时撤销功能 | 功能层级固定且少,或继承结构已经足够清晰 |
| 外观 | 简化复杂子系统 | 家庭影院系统、编译器前端、视频转换工具、微服务聚合层 | 需要为复杂子系统提供简单入口,需要分层解耦,需要为遗留系统提供新接口 | 子系统本身很简单,或客户端需要直接访问底层功能 |
| 享元 | 共享细粒度对象 | 文本编辑器字符渲染、游戏中的子弹/粒子系统、数据库连接池、字符串常量池 | 存在大量重复对象(>1000个),对象大部分状态可外部化,内存是瓶颈 | 对象数量少,或状态无法分离为内/外在状态 |
| 代理 | 控制对象访问 | 图片懒加载、权限控制(不同角色不同权限)、远程服务调用(RPC)、日志/性能监控 | 需要延迟加载(虚拟代理),需要访问控制(保护代理),需要添加透明操作(智能引用) | 不需要任何控制或附加操作,或代理类变得过于复杂 |
3.快速决策矩阵
| 遇到的情况 | 推荐模式 | 一句话理由 |
|---|---|---|
| 创建对象时有一堆if-else | 工厂方法 | 把创建逻辑封装起来 |
| 构造函数参数太多(>5个) | 建造者 | 链式调用,清晰优雅 |
| 需要复制一个复杂对象 | 原型模式 | 克隆比重新创建更高效 |
| 两个维度都在变化 | 桥接模式 | 避免类爆炸 |
| 第三方库接口不匹配 | 适配器 | 加一层转换 |
| 需要动态给对象加功能 | 装饰模式 | 比继承灵活 |
| 有树形结构需要处理 | 组合模式 | 统一对待叶子与容器 |
| 大量相似对象内存爆炸 | 享元模式 | 共享内在状态 |
| 复杂子系统难用 | 外观模式 | 给个简化入口 |
| 需要控制对象访问 | 代理模式 | 加个中间人 |