一、设计模式
设计模式(Design Patterns)是软件开发中反复出现问题的解决方案的通用描述 。
它是经过总结、提炼的高效代码结构和设计方案 ,帮助开发者写出更灵活、可维护和可扩展的代码。
优点 | 注意点 |
---|---|
规范代码结构,提高开发效率 | 设计模式不是银弹,需结合实际使用 |
促进代码复用和灵活扩展 | 滥用设计模式会导致过度设计 |
便于团队沟通和代码维护 | 理解设计模式背后的设计原则更重要 |
二、设计模式应用原则
原则 | 英文全称 | 核心思想 |
---|---|---|
单一职责原则(SRP) | Single Responsibility Principle | 一个类只负责一项职责,如变化只因一个原因而发生 |
开闭原则(OCP) | Open/Closed Principle | 对扩展开放,对修改关闭,鼓励使用抽象与继承 |
里氏替换原则(LSP) | Liskov Substitution Principle | 子类对象能替换父类对象而不影响程序正确性 |
接口隔离原则(ISP) | Interface Segregation Principle | 不应强迫用户依赖他们不使用的方法 |
依赖倒置原则(DIP) | Dependency Inversion Principle | 依赖于抽象而非具体实现(低层依赖高层) |
合成复用原则(CRP) | Composite Reuse Principle | 优先使用对象组合而非类继承 |
最少知识原则(LoD) | Law of Demeter | 只与直接朋友通信,降低类之间耦合 |
迪米特法则(Demeter) | Law of Demeter(LoD 的别称) | 同上,只交互需要交互的对象 |
三、设计模式的分类
设计模式一般分为三大类:
类别 | 作用 | 代表模式举例 |
---|---|---|
创建型模式 | 关注对象的创建,简化对象创建过程 | 单例(Singleton)、工厂(Factory)、抽象工厂(Abstract Factory)、建造者(Builder)、原型(Prototype) |
结构型模式 | 关注对象和类的组合 ,实现高效的结构设计 | 适配器(Adapter)、装饰器(Decorator)、代理(Proxy)、桥接(Bridge)、组合(Composite)、享元(Flyweight)、外观(Facade) |
行为型模式 | 关注对象之间的通信和职责分配 | 观察者(Observer)、策略(Strategy)、命令(Command)、状态(State)、职责链(Chain of Responsibility)、迭代器(Iterator)、中介者(Mediator)、备忘录(Memento) |
常见设计模式有单例模式、工厂模式、观察者模式、策略模式、代理模式、装饰器模式等。
四、常见设计模式------单例模式(Singleton Pattern)
1. 概念
单例模式 (Singleton Pattern)是一种创建型设计模式,目的是:
确保一个类只有一个实例,并提供一个全局访问点。
- 单例模式提供 全局唯一实例控制,是一种非常常用的基础模式;
- 在多线程环境下注意加锁 或使用现代 C++ 静态变量机制;
- 避免滥用,防止造成代码耦合和测试困难。
2. 使用场景
适用于以下场景:
场景描述 | 示例 |
---|---|
程序中只需要一个实例 | 日志记录器、线程池、数据库连接池 |
全局共享访问 | 配置管理器、计数器、注册表 |
控制资源访问 | 限制类实例个数,控制某些资源(如IO、GPU等) |
3. 核心特点
- 构造函数私有化,防止外部创建对象。
- 提供静态方法,返回唯一实例。
- 类中保存唯一实例的静态指针。
4. C++ 中的经典实现(线程安全 + 懒汉式)
cpp
#include <iostream>
using namespace std;
#include <mutex>
class Singleton {
private:
// 构造函数私有,防止外部构造
Singleton()
{
cout << "构造 Singleton 对象" << endl;
}
// 禁用拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance; // 静态指针
static std::mutex mtx; // 线程安全锁
public:
static Singleton* getInstance()
{
// 双重检查锁定(Double-Check Locking)
if (instance == nullptr)
{
lock_guard<mutex> lock(mtx); // 线程锁
if (instance == nullptr)
{
instance = new Singleton();
}
}
return instance;
}
void show()
{
cout << "这是 Singleton 实例" << endl;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
mutex Singleton::mtx;
// 测试
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->show();
if (s1 == s2)
cout << "两个实例相同,单例模式生效!" << endl;
return 0;
}
注意:
= delete
是 C++11 引入的一种机制,用来显式标记某个函数(包括构造、赋值、析构、普通函数)不允许使用。- 如果调用被
= delete
的函数,编译时就会报错,这比写成私有函数 + 空实现更安全、更清晰。
5. 变体说明
实现方式 | 是否线程安全 | 是否懒加载 | 特点 |
---|---|---|---|
懒汉式(如上) | ✅ | ✅ | 第一次调用时创建实例,性能较优 |
饿汉式(静态变量) | ✅ | ❌ | 程序加载时即创建,简单但可能浪费资源 |
C++11 静态局部变量 | ✅(自动线程安全) | ✅ | 推荐:C++11后局部静态变量是线程安全的 |
五、工厂模式(Factory Pattern)
1. 概念
工厂模式 是一种常见的创建型设计模式,核心目的是:
将对象的创建逻辑从使用者中解耦,由"工厂"统一创建对象。
2. 主要分类
模式名称 | 简要说明 |
---|---|
简单工厂模式 | 一个工厂类决定创建哪一种产品类 |
工厂方法模式 | 每个产品由一个具体工厂类负责创建 |
抽象工厂模式 | 生产多个产品族(多个产品等级结构),创建一组相关对象 |
3. 适用场景
- 对象创建过程复杂或变化频繁;
- 代码中存在大量
new
操作,难以维护; - 系统要根据不同条件动态决定创建哪一类对象。
4. 示例------工厂方法模式(Factory Method Pattern)
场景:不同的图形类(Circle
, Rectangle
),由对应的工厂类生成。
产品抽象类:
cpp
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
virtual ~Shape() = default;
};
具体产品类:
cpp
class Circle : public Shape {
public:
void draw() override
{
std::cout << "绘制圆形" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override
{
std::cout << "绘制矩形" << std::endl;
}
};
工厂抽象类:
cpp
class ShapeFactory {
public:
virtual Shape* createShape() = 0; // 创建产品接口
virtual ~ShapeFactory() = default;
};
具体工厂类:
cpp
class CircleFactory : public ShapeFactory {
public:
Shape* createShape() override
{
return new Circle();
}
};
class RectangleFactory : public ShapeFactory {
public:
Shape* createShape() override
{
return new Rectangle();
}
};
客户端使用:
cpp
int main() {
ShapeFactory* factory;
factory = new CircleFactory();
Shape* circle = factory->createShape();
circle->draw();
delete circle;
delete factory;
factory = new RectangleFactory();
Shape* rectangle = factory->createShape();
rectangle->draw();
delete rectangle;
delete factory;
return 0;
}
5. 优缺点分析
优点 | 缺点 |
---|---|
封装对象创建,调用者无需关心构造细节 | 每新增一个产品类就需要新增一个工厂类(类爆炸) |
易于扩展 ,遵循开闭原则 | 简单场景下可能显得过度设计 |
可以灵活切换不同产品,支持多态创建和调用 | 抽象层次增加,系统结构更复杂 |
六、观察者模式(Observer Pattern)
1. 概念
观察者模式 是一种行为型模式 ,用于建立一种一对多的依赖关系:
当一个对象(主题/被观察者 )的状态发生变化时,所有依赖于它的对象 (观察者)都会自动收到通知并更新。
2. 应用场景
场景 | 示例 |
---|---|
状态变化需要通知其他对象 | 图形界面事件(按钮点击) |
发布-订阅系统 | 消息队列、邮件通知、新闻订阅系统 |
数据模型与视图同步 | MVC 模式中的 Model 和 View |
3. 结构组成
角色 | 职责说明 |
---|---|
Subject(主题) | 维护观察者列表、添加/移除/通知观察者 |
Observer(观察者) | 接收通知并作出反应 |
ConcreteSubject | 实际的主题,状态发生变化时通知观察者 |
ConcreteObserver | 实际观察者,实现更新逻辑 |
4. C++ 示例代码
场景:气象站(WeatherStation)数据变化,通知多个显示设备(观察者)。
抽象观察者接口:
cpp
class Observer {
public:
virtual void update(float temperature) = 0;
virtual ~Observer() = default;
};
主题(Subject)接口:
cpp
#include <vector>
class Subject {
public:
virtual void attach(Observer* observer) = 0;
virtual void detach(Observer* observer) = 0;
virtual void notify() = 0;
virtual ~Subject() = default;
};
具体主题类:
cpp
class WeatherStation : public Subject {
private:
std::vector<Observer*> observers;
float temperature;
public:
void setTemperature(float temp)
{
temperature = temp;
notify(); // 数据变化时,通知所有观察者
}
void attach(Observer* observer) override
{
observers.push_back(observer);
}
void detach(Observer* observer) override
{
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notify() override {
for (Observer* o : observers)
{
o->update(temperature);
}
}
};
具体观察者类:
cpp
class PhoneDisplay : public Observer {
public:
void update(float temperature) override
{
std::cout << "[Phone] 当前温度:" << temperature << "°C" << std::endl;
}
};
class LEDDisplay : public Observer {
public:
void update(float temperature) override
{
std::cout << "[LED] 显示屏温度:" << temperature << "°C" << std::endl;
}
};
客户端使用:
cpp
int main() {
WeatherStation station;
PhoneDisplay phone;
LEDDisplay led;
station.attach(&phone);
station.attach(&led);
station.setTemperature(25.5f);
station.setTemperature(30.2f);
return 0;
}
5. 优缺点分析
优点 | 缺点 |
---|---|
解耦 :主题和观察者之间松散耦合 | 可能出现通知链过长,影响性能 |
易扩展:可动态添加/删除观察者 | 无法保证通知顺序,某些观察者可能对顺序敏感 |
符合"开闭原则":新增观察者不改动主题类 | 如果观察者太多,频繁通知可能带来性能开销 |
6. 实际应用
- Qt 信号槽机制;
- GUI 框架(按钮监听器);
- 消息发布/订阅(如 Kafka、RabbitMQ);
- Excel 单元格间依赖(值变化时联动)。
七、代理模式(Proxy Pattern)
1. 概念
代理模式 属于结构型设计模式,主要作用是:
为其他对象提供一种"代理"以控制对该对象的访问。
即通过一个中介(代理对象)来间接访问目标对象 ,可以增加访问控制、延迟加载、资源控制等功能。
2. 适用场景
场景描述 | 示例 |
---|---|
远程代理(Remote Proxy) | 远程方法调用,如 RPC 框架 |
虚拟代理(Virtual Proxy) | 延迟加载大型对象,如图片、视频加载 |
安全代理(Protection Proxy) | 控制权限访问,如权限验证、安全审计 |
智能代理(Smart Proxy) | 附加额外操作,如引用计数、日志记录等 |
3. 结构角色
角色 | 职责说明 |
---|---|
Subject | 抽象主题接口,定义目标行为 |
RealSubject | 真实对象,实现具体逻辑 |
Proxy | 代理对象,控制访问真实对象,可增强功能 |
4. C++ 示例------访问图像的代理模式(虚拟代理)
抽象接口(Subject):
cpp
class Image {
public:
virtual void display() = 0;
virtual ~Image() = default;
};
真实对象(RealSubject):
cpp
class RealImage : public Image {
private:
std::string filename;
public:
RealImage(const std::string& file) : filename(file)
{
loadFromDisk();
}
void loadFromDisk()
{
std::cout << "加载图片文件:" << filename << std::endl;
}
void display() override
{
std::cout << "显示图片:" << filename << std::endl;
}
};
代理对象(Proxy):
cpp
class ProxyImage : public Image {
private:
std::string filename;
RealImage* realImage = nullptr;
public:
ProxyImage(const std::string& file) : filename(file) {}
void display() override
{
if (!realImage)
{
realImage = new RealImage(filename); // 延迟加载
}
realImage->display();
}
~ProxyImage()
{
delete realImage;
}
};
客户端使用:
cpp
int main() {
Image* image = new ProxyImage("photo.jpg");
std::cout << "第一次调用 display() :" << std::endl;
image->display(); // 会加载 + 显示
std::cout << "第二次调用 display() :" << std::endl;
image->display(); // 直接显示,不再加载
delete image;
return 0;
}
5. 优缺点分析
优点 | 缺点 |
---|---|
控制对象访问(权限、安全、远程调用) | 增加系统复杂度 |
可延迟加载资源,提升性能(如虚拟代理) | 若过度使用,可能增加维护难度 |
可增加额外功能(如日志、缓存、引用计数等) | 被代理对象接口变化时,代理类也需修改 |
6. 实际应用
- 虚拟代理:大型图像延迟加载(如网页图片懒加载);
- 安全代理:数据库连接权限控制;
- 智能代理 :引用计数类(如智能指针
std::shared_ptr
); - 远程代理:RPC、gRPC 框架中的远程服务调用。
八、策略模式(Strategy Pattern)
1. 概念
策略模式 是一种典型的行为型设计模式,用于:
将一系列可互换的算法(或策略)封装起来,使它们可以独立于使用它们的客户端自由切换。
2. 适用场景
适用场景 | 示例 |
---|---|
存在多个算法/行为,可以灵活切换 | 支付方式(微信/支付宝)、排序算法选择等 |
使用 if-else / switch 过多 |
用策略类代替繁杂条件逻辑 |
系统需要在运行时动态选择行为 | 游戏角色攻击方式、文件压缩算法(ZIP、RAR)等 |
3. 结构组成
角色 | 职责说明 |
---|---|
Strategy | 抽象策略类,定义算法接口 |
ConcreteStrategy | 具体策略类,实现不同算法或行为 |
Context | 上下文类,持有策略对象,供客户端使用 |
4. C++ 示例------支付策略(微信、支付宝)
抽象策略类:
cpp
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
具体策略类:
cpp
class WeChatPay : public PaymentStrategy {
public:
void pay(int amount) override
{
std::cout << "使用微信支付:" << amount << " 元" << std::endl;
}
};
class Alipay : public PaymentStrategy {
public:
void pay(int amount) override
{
std::cout << "使用支付宝支付:" << amount << " 元" << std::endl;
}
};
上下文类(使用策略):
cpp
class PaymentContext {
private:
PaymentStrategy* strategy;
public:
PaymentContext(PaymentStrategy* strategy) : strategy(strategy) {}
void setStrategy(PaymentStrategy* newStrategy)
{
strategy = newStrategy;
}
void pay(int amount)
{
strategy->pay(amount);
}
};
客户端使用示例:
cpp
int main() {
WeChatPay wechat;
Alipay alipay;
PaymentContext context(&wechat);
context.pay(100); // 使用微信支付
context.setStrategy(&alipay);
context.pay(200); // 使用支付宝支付
return 0;
}
5. 优缺点分析
优点 | 缺点 |
---|---|
算法可以自由切换,互不影响,符合开闭原则 | 客户端需要了解不同策略类以选择使用 |
消除了大量 if-else ,逻辑更清晰 |
增加了类数量,每个策略都要写一个类 |
可以在运行时动态选择策略,实现更灵活的行为扩展 | 若算法内部差异很小,会导致重复代码 |
6. 现实应用
- 排序器中选择不同排序策略(快速排序、归并排序等);
- 游戏中角色选择攻击行为(近战、远程、魔法);
- 视频播放器根据网络状况切换清晰度策略。
九、装饰器模式(Decorator Pattern)
1. 概念
装饰器模式 (Decorator)是一种结构型设计模式,用于:
在不改变原始类代码 的前提下,动态地为对象添加额外的功能。
2. 使用场景
使用场景 | 示例 |
---|---|
动态添加/移除对象功能 | I/O 流(如 std::iostream ) |
避免子类爆炸式扩展(继承过多) | 代替多子类继承组合 |
运行时灵活组合对象行为 | GUI 控件样式、文本处理管道等 |
3. 结构角色
角色 | 职责说明 |
---|---|
Component | 抽象接口,定义可以装饰的操作行为 |
ConcreteComponent | 具体对象,定义基本行为实现 |
Decorator | 抽象装饰器,包含一个 Component 成员 |
ConcreteDecorator | 实际装饰器,增加具体功能 |
4. C++ 示例------饮料加调料系统
抽象组件接口(Component):
cpp
class Beverage {
public:
virtual std::string getDescription() = 0;
virtual double cost() = 0;
virtual ~Beverage() = default;
};
具体组件(ConcreteComponent):
cpp
class Coffee : public Beverage {
public:
std::string getDescription() override
{
return "Coffee";
}
double cost() override
{
return 5.0;
}
};
抽象装饰器(Decorator):
cpp
class CondimentDecorator : public Beverage {
protected:
Beverage* beverage;
public:
CondimentDecorator(Beverage* b) : beverage(b) {}
};
具体装饰器(ConcreteDecorator):
cpp
class Milk : public CondimentDecorator {
public:
Milk(Beverage* b) : CondimentDecorator(b) {}
std::string getDescription() override
{
return beverage->getDescription() + ", Milk";
}
double cost() override
{
return beverage->cost() + 1.5;
}
};
class Sugar : public CondimentDecorator {
public:
Sugar(Beverage* b) : CondimentDecorator(b) {}
std::string getDescription() override
{
return beverage->getDescription() + ", Sugar";
}
double cost() override
{
return beverage->cost() + 0.5;
}
};
客户端使用:
cpp
int main() {
Beverage* beverage = new Coffee(); // 基础咖啡
beverage = new Milk(beverage); // 加牛奶
beverage = new Sugar(beverage); // 加糖
std::cout << "饮料描述: " << beverage->getDescription() << std::endl;
std::cout << "总价: " << beverage->cost() << " 元" << std::endl;
delete beverage; // 实际应逐层释放
return 0;
}
5. 优缺点分析
优点 | 缺点 |
---|---|
不改变原始类,实现"开闭原则" | 类数目可能增多 |
支持功能组合,灵活拓展 | 多层嵌套可能导致调试困难 |
替代继承层级,避免子类爆炸式增长 | 对象创建较多(每层一个) |
6. 现实应用
- Java I/O 流 (
BufferedInputStream
,DataInputStream
等); - 图形组件样式叠加(边框、阴影、背景);
- 日志增强、权限控制模块;
- C++ 中的流操作符 (如
std::ostream
装饰行为),
十、常用设计模式的比较
模式名称 | 目的 | 典型应用场景 | 优点 | 缺点 |
---|---|---|---|---|
单例模式 | 保证一个类只有一个实例,并提供全局访问点 | 配置管理器、线程池、数据库连接池、日志管理器 | 控制实例数量、节省资源、提供全局访问点 | 不易扩展、对测试不友好、多线程需加锁维护单例安全 |
工厂模式 | 将对象的创建过程封装起来,客户端无需了解对象创建的具体细节 | 创建复杂对象、大量具有共性但不完全相同的对象 | 解耦创建和使用,增强扩展性和灵活性 | 增加系统复杂性,类数量增多 |
观察者模式 | 建立一对多的依赖关系,一旦主题对象状态变化,所有观察者都会收到通知 | 消息订阅系统、事件驱动模型、GUI 事件处理、MVC 模式 | 实现解耦,支持广播通信,符合开闭原则 | 可能引发性能问题或通知混乱,观察者之间存在隐式依赖 |
策略模式 | 将算法封装为独立的策略类,使其可以互换,以便在运行时选择不同的行为 | 算法族(如排序、压缩)、游戏 AI 策略、支付方式选择 | 策略独立、便于切换、符合开闭原则 | 增加类数量,客户端必须了解不同策略的作用 |
代理模式 | 通过代理对象控制对目标对象的访问,可添加额外控制逻辑(如权限、懒加载等) | 安全控制、延迟加载、远程调用、对象池、日志记录 | 控制对象访问、增强原对象功能、不改变原有类 | 增加系统复杂性,可能引入性能开销 |
装饰器模式 | 动态地为对象添加额外职责,不修改其结构,实现行为扩展 | IO流处理、图形组件增强、权限验证、日志记录、缓存处理 | 比继承更灵活,职责细化、符合开闭原则 | 多层装饰可能导致调试困难,创建大量装饰类 |
使用建议:
- 要 控制对象数量 → 单例模式;
- 要 封装对象创建逻辑 → 工厂模式;
- 要 对象间解耦并实时通知 → 观察者模式;
- 要 算法/行为灵活切换 → 策略模式;
- 要 控制对象访问/增强功能 → 代理模式;
- 要 在不改变原对象的基础上扩展功能 → 装饰器模式。