在 C++ 领域,设计模式不仅帮助开发者构建可维护、可扩展的软件结构,还能与现代语言特性深度融合。本文将以现代 C++(C++11 及以后)为背景,探讨六大经典设计模式在实际项目中的实现与优化。我们将重点关注:单例(Singleton)、工厂(Factory)、观察者(Observer)、策略(Strategy)、适配器(Adapter)和代理(Proxy)。通过代码示例和性能分析,帮助读者在项目中正确、有效地使用这些模式。
1. 单例模式(Singleton)
1.1 传统实现
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 guarantees thread-safe initialization
return instance;
}
// 其他公共接口
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
- 优点:易于使用、内置线程安全(C++11 之后)。
- 缺点:难以单元测试、全局状态易导致隐藏耦合。
1.2 依赖注入替代
在现代 C++ 代码中,优先通过构造函数注入(Dependency Injection, DI)传递单例依赖,而不是直接使用 Singleton::instance()。例如:
class Service {
public:
explicit Service(Singleton& s) : m_singleton(s) {}
private:
Singleton& m_singleton;
};
这样可在单元测试时使用 Mock 或者自定义实现替代真实单例。
2. 工厂模式(Factory)
2.1 抽象工厂
class Widget {
public:
virtual void draw() = 0;
};
class Button : public Widget {
public:
void draw() override { std::cout << "Button\n"; }
};
class TextBox : public Widget {
public:
void draw() override { std::cout << "TextBox\n"; }
};
class WidgetFactory {
public:
static std::unique_ptr <Widget> create(const std::string& type) {
if (type == "button") return std::make_unique <Button>();
if (type == "textbox") return std::make_unique <TextBox>();
throw std::invalid_argument("Unknown widget");
}
};
- 特点:使用
std::unique_ptr负责对象生命周期,避免裸指针。 - 扩展:通过注册机制(例如
std::unordered_map)实现插件式工厂。
2.2 变体:模板工厂
template<typename T>
class TemplateFactory {
public:
static std::unique_ptr <T> create() { return std::make_unique<T>(); }
};
适用于需要在编译期确定工厂类型的场景,提升类型安全。
3. 观察者模式(Observer)
3.1 经典实现
class Observer {
public:
virtual void update() = 0;
};
class Subject {
public:
void attach(Observer* o) { m_observers.push_back(o); }
void detach(Observer* o) {
m_observers.erase(std::remove(m_observers.begin(), m_observers.end(), o), m_observers.end());
}
void notify() {
for (auto* o : m_observers) o->update();
}
private:
std::vector<Observer*> m_observers;
};
- 缺点:Observer 存在裸指针,易导致悬空指针。
3.2 现代化改进
使用 std::weak_ptr 或 std::shared_ptr 维护观察者关系:
class Subject {
public:
void attach(std::weak_ptr <Observer> o) { m_observers.push_back(o); }
void notify() {
for (auto it = m_observers.begin(); it != m_observers.end();) {
if (auto shared = it->lock()) {
shared->update();
++it;
} else { // 观察者已销毁
it = m_observers.erase(it);
}
}
}
private:
std::vector<std::weak_ptr<Observer>> m_observers;
};
这样即可安全处理生命周期,避免野指针。
4. 策略模式(Strategy)
4.1 基本实现
class SortStrategy {
public:
virtual void sort(std::vector <int>& data) = 0;
};
class BubbleSort : public SortStrategy {
public:
void sort(std::vector <int>& data) override { /* bubble sort */ }
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector <int>& data) override { /* quick sort */ }
};
class Context {
public:
explicit Context(std::unique_ptr <SortStrategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr <SortStrategy> s) { strategy = std::move(s); }
void execute(std::vector <int>& data) { strategy->sort(data); }
private:
std::unique_ptr <SortStrategy> strategy;
};
- 优势:算法可在运行时切换,易于扩展。
- 注意:若策略包含大量状态,可考虑使用状态模式或组合模式。
5. 适配器模式(Adapter)
5.1 适配老接口
class OldPrinter {
public:
void printOld(const std::string& s) { std::cout << "Old: " << s << '\n'; }
};
class NewPrinter {
public:
void printNew(const std::string& s) { std::cout << "New: " << s << '\n'; }
};
class PrinterAdapter {
public:
explicit PrinterAdapter(std::unique_ptr <NewPrinter> p) : newPrinter(std::move(p)) {}
void printOld(const std::string& s) { newPrinter->printNew(s); }
private:
std::unique_ptr <NewPrinter> newPrinter;
};
- 好处:保持旧系统兼容,逐步迁移到新接口。
- 注意:适配器不应把接口改为旧接口的成员函数,而是提供统一接口。
6. 代理模式(Proxy)
6.1 虚拟代理(Lazy Loading)
class HeavyResource {
public:
HeavyResource() { std::cout << "Initializing heavy resource\n"; }
void use() { std::cout << "Using heavy resource\n"; }
};
class HeavyResourceProxy {
public:
void use() {
if (!resource) resource = std::make_unique <HeavyResource>();
resource->use();
}
private:
std::unique_ptr <HeavyResource> resource;
};
- 优点:按需加载,减少启动成本。
- 挑战:线程安全。可通过
std::call_once或std::atomic确保一次性初始化。
7. 性能与内存考虑
- 对象池:对于频繁创建销毁的对象(如网络包、线程句柄),实现对象池能显著减少堆内存碎片。
- RAII:所有模式均应遵循 RAII(资源获取即初始化)原则,使用
std::unique_ptr、std::shared_ptr、std::optional等包装资源。 - 内联与constexpr:对于小型策略/工厂函数,使用
inline或constexpr可让编译器在编译期完成调用。 - 模板元编程:在编译期选择算法(如排序)可提升性能,尤其在嵌入式/高性能场景。
8. 小结
现代 C++ 为经典设计模式提供了强大的语言支持:线程安全的局部静态变量、智能指针、模板与 constexpr、以及并行/异步库。通过这些工具,设计模式不再是理论,而是可直接、可维护的代码模式。正确使用模式可以显著提升代码可读性与可维护性,但也要警惕过度设计。选择合适的模式、保持代码简洁才是最终目标。
如有更具体的使用场景或代码难点,欢迎继续交流讨论。祝编码愉快!