1. 设计模式概述与学习路径
设计模式是面向对象编程中解决特定问题的经典方案模板,它代表了最佳实践经验的总结。在C++开发中,掌握设计模式能帮助我们构建更灵活、可维护的代码结构。设计模式共有23种,但实际开发中常用的约10种左右。
1.1 设计模式的核心价值
设计模式的核心价值体现在三个方面:
- 代码复用:提供经过验证的解决方案模板,避免重复造轮子
- 解耦与扩展:通过合理的抽象和接口设计,降低模块间的耦合度
- 团队协作:提供统一的术语和设计思路,提高团队沟通效率
1.2 学习设计模式的正确方法
学习设计模式需要遵循以下步骤:
- 理解问题场景:明确该模式要解决的具体问题
- 分析稳定点与变化点:识别系统中不变的部分和可能变化的部分
- 掌握结构图:理解类之间的关系和交互方式
- 联系设计原则:思考模式背后的设计原则(如开闭原则、依赖倒置等)
- 实践典型应用:通过实际案例加深理解
关键提示:设计模式不是银弹,过度使用会导致代码复杂度上升。应根据实际需求合理选择,优先考虑简单直接的解决方案。
2. 模板方法模式
2.1 模式定义与适用场景
模板方法模式定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。它适用于以下场景:
- 多个类有相同的方法组合,但具体实现不同
- 需要控制子类的扩展点,避免整体结构被破坏
- 存在一系列固定步骤的流程,但某些步骤需要灵活变化
2.2 实现原理与代码分析
模板方法通过抽象基类实现,包含两种类型的方法:
- 基本方法:抽象方法或虚函数,由子类实现
- 模板方法:定义算法骨架,调用基本方法
cpp复制class DocumentProcessor {
public:
// 模板方法
void ProcessDocument() {
OpenDocument();
if (NeedAnalyze()) {
AnalyzeContent();
}
SaveDocument();
Cleanup();
}
protected:
virtual void OpenDocument() = 0;
virtual void AnalyzeContent() = 0;
virtual void SaveDocument() = 0;
// 钩子方法(可选步骤)
virtual bool NeedAnalyze() { return true; }
void Cleanup() {
// 公共实现
cout << "Cleaning resources..." << endl;
}
};
class PDFProcessor : public DocumentProcessor {
protected:
void OpenDocument() override {
cout << "Opening PDF file" << endl;
}
void AnalyzeContent() override {
cout << "Analyzing PDF content" << endl;
}
void SaveDocument() override {
cout << "Saving as PDF" << endl;
}
bool NeedAnalyze() override {
return false; // PDF不需要内容分析
}
};
2.3 实践技巧与注意事项
- 访问控制:模板方法通常声明为public,基本方法声明为protected
- 钩子方法:通过虚函数提供可选扩展点,增强灵活性
- 避免滥用:当算法步骤变化较大时,考虑策略模式可能更合适
- 性能考量:虚函数调用有额外开销,在性能敏感场景需谨慎
3. 观察者模式
3.1 模式定义与适用场景
观察者模式定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知。典型应用场景包括:
- 事件处理系统
- 数据监控与报警
- GUI组件交互
- 发布-订阅系统
3.2 实现原理与代码分析
观察者模式包含两个主要角色:
- Subject:维护观察者列表,提供注册/注销接口,状态变化时通知观察者
- Observer:定义更新接口,供Subject调用
cpp复制class IObserver {
public:
virtual ~IObserver() = default;
virtual void Update(const string& message) = 0;
};
class ISubject {
public:
virtual ~ISubject() = default;
virtual void Attach(IObserver* observer) = 0;
virtual void Detach(IObserver* observer) = 0;
virtual void Notify() = 0;
};
class NewsPublisher : public ISubject {
list<IObserver*> observers_;
string latest_news_;
public:
void Attach(IObserver* observer) override {
observers_.push_back(observer);
}
void Detach(IObserver* observer) override {
observers_.remove(observer);
}
void Notify() override {
for (auto observer : observers_) {
observer->Update(latest_news_);
}
}
void PublishNews(const string& news) {
latest_news_ = news;
Notify();
}
};
class EmailSubscriber : public IObserver {
void Update(const string& message) override {
cout << "Email sent with news: " << message << endl;
}
};
class SMSSubscriber : public IObserver {
void Update(const string& message) override {
cout << "SMS sent with news: " << message << endl;
}
};
3.3 实践技巧与注意事项
- 线程安全:在多线程环境中使用时需要加锁保护观察者列表
- 通知顺序:观察者被通知的顺序通常是不确定的,不应依赖特定顺序
- 性能优化:对于频繁更新的Subject,考虑批量通知或节流机制
- 内存管理:使用weak_ptr避免观察者生命周期问题
4. 策略模式
4.1 模式定义与适用场景
策略模式定义一系列算法,将每个算法封装起来,并使它们可以互相替换。适用于:
- 需要动态切换算法或策略的场景
- 有多个条件分支的复杂逻辑
- 算法需要独立于使用它的客户端变化
4.2 实现原理与代码分析
策略模式包含三个角色:
- Context:维护策略引用,提供接口给客户端
- Strategy:定义算法接口
- ConcreteStrategy:具体算法实现
cpp复制class ISortStrategy {
public:
virtual ~ISortStrategy() = default;
virtual void Sort(vector<int>& data) = 0;
};
class QuickSort : public ISortStrategy {
void Sort(vector<int>& data) override {
cout << "Sorting using QuickSort" << endl;
// 实际快速排序实现
}
};
class MergeSort : public ISortStrategy {
void Sort(vector<int>& data) override {
cout << "Sorting using MergeSort" << endl;
// 实际归并排序实现
}
};
class Sorter {
unique_ptr<ISortStrategy> strategy_;
public:
explicit Sorter(unique_ptr<ISortStrategy> strategy)
: strategy_(move(strategy)) {}
void SetStrategy(unique_ptr<ISortStrategy> strategy) {
strategy_ = move(strategy);
}
void ExecuteSort(vector<int>& data) {
strategy_->Sort(data);
}
};
// 使用示例
vector<int> data = {5, 2, 7, 1, 9};
Sorter sorter(make_unique<QuickSort>());
sorter.ExecuteSort(data);
sorter.SetStrategy(make_unique<MergeSort>());
sorter.ExecuteSort(data);
4.3 实践技巧与注意事项
- 策略创建:可以使用工厂模式创建策略对象
- 无状态策略:如果策略无状态,可以共享同一个实例
- 与模板方法对比:策略模式通过组合切换整个算法,模板方法通过继承修改部分步骤
- 性能考量:频繁创建销毁策略对象可能影响性能,考虑对象池
5. 单例模式
5.1 模式定义与适用场景
单例模式确保一个类只有一个实例,并提供一个全局访问点。适用于:
- 需要全局唯一访问点的资源(如配置、日志、连接池)
- 创建成本高的对象
- 需要严格控制实例数量的场景
5.2 线程安全实现
C++11之后最简洁安全的实现方式是Meyer's Singleton:
cpp复制class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
// 删除拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
5.3 高级用法与注意事项
- 延迟初始化:C++11保证局部静态变量初始化是线程安全的
- 销毁时机:单例在程序结束时自动销毁,依赖它的对象需注意销毁顺序
- 测试困难:单例可能导致单元测试困难,考虑依赖注入
- 替代方案:对于需要多个实例但数量受限的场景,可以使用对象池模式
关键提示:单例模式常被过度使用,在大多数情况下,依赖注入是更好的选择,因为它提高了代码的可测试性和灵活性。
6. 工厂模式
6.1 模式定义与适用场景
工厂模式定义一个创建对象的接口,但让子类决定实例化哪个类。适用于:
- 创建逻辑复杂,需要封装
- 需要解耦创建过程和使用过程
- 系统需要支持多种产品变体
6.2 实现原理与代码分析
工厂模式包含以下角色:
- Product:定义产品接口
- ConcreteProduct:具体产品实现
- Creator:声明工厂方法
- ConcreteCreator:实现工厂方法
cpp复制class IButton {
public:
virtual ~IButton() = default;
virtual void Render() = 0;
virtual void OnClick() = 0;
};
class WindowsButton : public IButton {
void Render() override {
cout << "Windows style button rendered" << endl;
}
void OnClick() override {
cout << "Windows button clicked" << endl;
}
};
class MacButton : public IButton {
void Render() override {
cout << "Mac style button rendered" << endl;
}
void OnClick() override {
cout << "Mac button clicked" << endl;
}
};
class Dialog {
public:
virtual ~Dialog() = default;
virtual unique_ptr<IButton> CreateButton() = 0;
void RenderDialog() {
auto button = CreateButton();
button->Render();
}
};
class WindowsDialog : public Dialog {
unique_ptr<IButton> CreateButton() override {
return make_unique<WindowsButton>();
}
};
class MacDialog : public Dialog {
unique_ptr<IButton> CreateButton() override {
return make_unique<MacButton>();
}
};
6.3 实践技巧与注意事项
- 与简单工厂区别:工厂方法将创建逻辑分散到子类,符合开闭原则
- 依赖倒置:客户端代码依赖抽象接口而非具体类
- 扩展性:新增产品类型只需添加新的Creator子类
- 结合模板:可以使用模板减少重复代码
7. 抽象工厂模式
7.1 模式定义与适用场景
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。适用于:
- 系统需要独立于其产品的创建、组合和表示
- 系统需要配置多个产品族中的一个
- 需要强调一系列相关产品对象的设计以便联合使用
7.2 实现原理与代码分析
抽象工厂包含:
- AbstractFactory:声明创建抽象产品的方法
- ConcreteFactory:实现创建具体产品的方法
- AbstractProduct:声明产品接口
- ConcreteProduct:具体产品实现
cpp复制// GUI组件示例
class ICheckbox {
public:
virtual ~ICheckbox() = default;
virtual void Paint() = 0;
};
class ITextbox {
public:
virtual ~ITextbox() = default;
virtual void Render() = 0;
};
class WinCheckbox : public ICheckbox {
void Paint() override {
cout << "Windows checkbox painted" << endl;
}
};
class MacCheckbox : public ICheckbox {
void Paint() override {
cout << "Mac checkbox painted" << endl;
}
};
class WinTextbox : public ITextbox {
void Render() override {
cout << "Windows textbox rendered" << endl;
}
};
class MacTextbox : public ITextbox {
void Render() override {
cout << "Mac textbox rendered" << endl;
}
};
class IGUIFactory {
public:
virtual ~IGUIFactory() = default;
virtual unique_ptr<ICheckbox> CreateCheckbox() = 0;
virtual unique_ptr<ITextbox> CreateTextbox() = 0;
};
class WinFactory : public IGUIFactory {
unique_ptr<ICheckbox> CreateCheckbox() override {
return make_unique<WinCheckbox>();
}
unique_ptr<ITextbox> CreateTextbox() override {
return make_unique<WinTextbox>();
}
};
class MacFactory : public IGUIFactory {
unique_ptr<ICheckbox> CreateCheckbox() override {
return make_unique<MacCheckbox>();
}
unique_ptr<ITextbox> CreateTextbox() override {
return make_unique<MacTextbox>();
}
};
7.3 实践技巧与注意事项
- 产品族一致性:确保工厂创建的产品是兼容的
- 扩展困难:添加新产品需要修改抽象工厂接口
- 与工厂方法区别:抽象工厂创建产品家族,工厂方法创建单一产品
- 实际应用:常用于跨平台UI、数据库访问等场景
8. 责任链模式
8.1 模式定义与适用场景
责任链模式使多个对象都有机会处理请求,从而避免请求发送者与接收者耦合。适用于:
- 多个对象可以处理请求,但具体由哪个对象处理在运行时确定
- 需要动态指定处理请求的对象集合
- 请求需要被多个对象处理中的一个或多个处理
8.2 实现原理与代码分析
责任链模式包含:
- Handler:定义处理请求的接口,实现后继链
- ConcreteHandler:处理它负责的请求,可访问后继者
cpp复制class PurchaseRequest {
public:
double amount;
string purpose;
PurchaseRequest(double amt, const string& pur)
: amount(amt), purpose(pur) {}
};
class Approver {
protected:
unique_ptr<Approver> successor;
string name;
double approvalLimit;
public:
Approver(const string& name, double limit)
: name(name), approvalLimit(limit) {}
void SetSuccessor(unique_ptr<Approver> next) {
successor = move(next);
}
virtual void ProcessRequest(PurchaseRequest request) {
if (request.amount <= approvalLimit) {
cout << name << " approved request for "
<< request.purpose << endl;
} else if (successor) {
successor->ProcessRequest(request);
} else {
cout << "Request for " << request.purpose
<< " requires executive meeting!" << endl;
}
}
};
class Manager : public Approver {
public:
Manager(const string& name) : Approver(name, 10000) {}
};
class Director : public Approver {
public:
Director(const string& name) : Approver(name, 50000) {}
};
class VicePresident : public Approver {
public:
VicePresident(const string& name) : Approver(name, 100000) {}
};
// 使用示例
auto manager = make_unique<Manager>("张经理");
auto director = make_unique<Director>("李总监");
auto vp = make_unique<VicePresident>("王副总");
manager->SetSuccessor(move(director));
director->SetSuccessor(move(vp));
PurchaseRequest requests[] = {
{5000, "Office supplies"},
{25000, "Conference equipment"},
{80000, "Server upgrade"},
{150000, "New department budget")
};
for (auto& req : requests) {
manager->ProcessRequest(req);
}
8.3 实践技巧与注意事项
- 请求终止:明确处理请求后是否继续传递
- 性能考量:长链可能导致性能问题,考虑其他模式
- 动态修改:运行时可以动态修改链结构
- 与装饰器区别:责任链强调请求处理,装饰器强调功能添加
9. 装饰器模式
9.1 模式定义与适用场景
装饰器模式动态地给对象添加额外职责,比继承更灵活。适用于:
- 需要在不影响其他对象的情况下,动态、透明地添加职责
- 需要撤销职责
- 通过继承扩展不切实际时
9.2 实现原理与代码分析
装饰器模式包含:
- Component:定义对象接口
- ConcreteComponent:具体实现
- Decorator:维护Component引用并定义装饰接口
- ConcreteDecorator:添加具体职责
cpp复制class IDataSource {
public:
virtual ~IDataSource() = default;
virtual void WriteData(const string& data) = 0;
virtual string ReadData() = 0;
};
class FileDataSource : public IDataSource {
string filename;
string data;
public:
explicit FileDataSource(const string& name) : filename(name) {}
void WriteData(const string& data) override {
cout << "Writing data to file " << filename << endl;
this->data = data;
}
string ReadData() override {
cout << "Reading data from file " << filename << endl;
return data;
}
};
class DataSourceDecorator : public IDataSource {
protected:
unique_ptr<IDataSource> wrappee;
public:
explicit DataSourceDecorator(unique_ptr<IDataSource> source)
: wrappee(move(source)) {}
void WriteData(const string& data) override {
wrappee->WriteData(data);
}
string ReadData() override {
return wrappee->ReadData();
}
};
class EncryptionDecorator : public DataSourceDecorator {
public:
using DataSourceDecorator::DataSourceDecorator;
void WriteData(const string& data) override {
string encrypted = "Encrypted(" + data + ")";
wrappee->WriteData(encrypted);
}
string ReadData() override {
string data = wrappee->ReadData();
return data.substr(10, data.length() - 11); // 简单解密
}
};
class CompressionDecorator : public DataSourceDecorator {
public:
using DataSourceDecorator::DataSourceDecorator;
void WriteData(const string& data) override {
string compressed = "Compressed(" + data + ")";
wrappee->WriteData(compressed);
}
string ReadData() override {
string data = wrappee->ReadData();
return data.substr(11, data.length() - 12); // 简单解压
}
};
// 使用示例
auto source = make_unique<FileDataSource>("data.txt");
auto encrypted = make_unique<EncryptionDecorator>(move(source));
auto compressed = make_unique<CompressionDecorator>(move(encrypted));
compressed->WriteData("Design Patterns");
string data = compressed->ReadData();
9.3 实践技巧与注意事项
- 接口一致性:装饰器必须与被装饰对象接口一致
- 多层装饰:可以嵌套多个装饰器
- 与继承对比:装饰器提供运行时灵活性,继承是静态的
- 性能影响:多层装饰可能影响性能
10. 组合模式
10.1 模式定义与适用场景
组合模式将对象组合成树形结构以表示"部分-整体"层次结构,使客户端对单个对象和组合对象的使用具有一致性。适用于:
- 表示对象的整体-部分层次结构
- 希望客户端忽略组合对象与单个对象的不同
- 处理树形结构数据
10.2 实现原理与代码分析
组合模式包含:
- Component:声明组合中对象的接口
- Leaf:叶子节点,无子节点
- Composite:存储子组件,实现与子组件相关操作
cpp复制class FileSystemComponent {
public:
virtual ~FileSystemComponent() = default;
virtual string GetName() const = 0;
virtual size_t GetSize() const = 0;
virtual void Add(unique_ptr<FileSystemComponent> component) {
throw runtime_error("Unsupported operation");
}
virtual void Print(int depth = 0) const = 0;
};
class File : public FileSystemComponent {
string name;
size_t size;
public:
File(const string& n, size_t s) : name(n), size(s) {}
string GetName() const override { return name; }
size_t GetSize() const override { return size; }
void Print(int depth = 0) const override {
cout << string(depth, '\t') << "File: " << name
<< ", Size: " << size << " bytes" << endl;
}
};
class Directory : public FileSystemComponent {
string name;
vector<unique_ptr<FileSystemComponent>> children;
public:
explicit Directory(const string& n) : name(n) {}
string GetName() const override { return name; }
size_t GetSize() const override {
size_t total = 0;
for (const auto& child : children) {
total += child->GetSize();
}
return total;
}
void Add(unique_ptr<FileSystemComponent> component) override {
children.push_back(move(component));
}
void Print(int depth = 0) const override {
cout << string(depth, '\t') << "Directory: " << name << endl;
for (const auto& child : children) {
child->Print(depth + 1);
}
}
};
// 使用示例
auto root = make_unique<Directory>("Root");
auto documents = make_unique<Directory>("Documents");
auto images = make_unique<Directory>("Images");
documents->Add(make_unique<File>("resume.doc", 250));
documents->Add(make_unique<File>("report.pdf", 1024));
images->Add(make_unique<File>("photo.jpg", 2048));
images->Add(make_unique<File>("diagram.png", 512));
root->Add(move(documents));
root->Add(move(images));
root->Print();
cout << "Total size: " << root->GetSize() << " bytes" << endl;
10.3 实践技巧与注意事项
- 透明性:使组件接口包含所有操作,叶子节点对不支持的操作抛出异常
- 缓存:对于频繁访问的计算结果(如大小),考虑缓存
- 遍历顺序:明确子组件的遍历顺序
- 内存管理:使用智能指针自动管理组件生命周期
11. 设计模式综合应用建议
在实际项目中应用设计模式时,需要注意以下几点:
- 避免过度设计:不是所有问题都需要设计模式,简单直接的解决方案往往更好
- 模式组合:多个模式可以结合使用,如工厂+策略、观察者+命令等
- 语言特性:C++特有的RAII、模板等特性可以简化某些模式的实现
- 性能考量:虚函数调用、对象创建等可能影响性能,在关键路径需谨慎
- 团队共识:确保团队成员对使用的模式有共同理解
设计模式的学习是一个渐进的过程,建议从理解原则开始,然后通过实际项目逐步掌握各种模式的应用场景和实现技巧。记住,模式是工具,而不是目标,最终目的是写出清晰、可维护、可扩展的代码。