C++ 设计模式:何时使用及为何使用
\n\n你是否曾因新功能破坏了旧架构而第十次重写代码?或者花费数小时调试,试图找出谁修改了全局变量以及为什么修改?听起来很熟悉?那么欢迎来到设计模式的世界。
\n\n模式并非万能药。它们是经过实战检验的常见问题解决方案。它们不会让你的代码完美,但会帮助你与其他开发者使用相同的语言交流,并避免成千上万的程序员在你之前已经踩过的坑。
\n\n在本文中,我们将剖析 C++ 中三大类模式:创建型、结构型 和 行为型。我们将学习何时应用它们,以及何时最好避开它们。开始吧!
\n\n创建型模式:控制对象的创建
\n\n创建型模式负责灵活地创建对象。它们隐藏了实例化逻辑,使系统独立于其组件的具体创建方式。
\n\n单例模式
\n\n经典模式。它确保一个类只有一个实例,并提供全局访问点。听起来很诱人,但要明智使用 —— 如果单例膨胀成“上帝对象”,它往往会变成反模式。
\n\n何时使用:日志系统、配置管理器、线程池、硬件交互。
\n\nC++ 示例(线程安全,C++11 及更新版本):
\n\nclass Logger {\npublic:\n static Logger& getInstance() {\n static Logger instance; // 即使在多线程中也只初始化一次\n return instance;\n }\n\n void log(const std::string& msg) {\n std::cout << "[LOG]: " << msg << std::endl;\n }\n\n // 禁止复制和赋值\n Logger(const Logger&) = delete;\n Logger& operator=(const Logger&) = delete;\n\nprivate:\n Logger() = default; // 私有构造函数\n ~Logger() = default;\n};\n\n// 使用\nLogger::getInstance().log("系统启动");\n\n工厂方法
\n\n定义创建对象的接口,但让子类决定实例化哪个类。当你不确定具体需要哪个类时,它非常有用。
\n\n何时使用:解析不同格式(JSON、XML、YAML)、为不同操作系统创建 UI 元素、与不同数据库交互。
\n\n示例:
\n\n// 抽象产品\nclass Document {\npublic:\n virtual void open() = 0;\n virtual ~Document() = default;\n};\n\n// 具体产品\nclass PDFDocument : public Document {\n void open() override { std::cout << "打开 PDF" << std::endl; }\n};\n\nclass WordDocument : public Document {\n void open() override { std::cout << "打开 Word" << std::endl; }\n};\n\n// 工厂\nclass Application {\npublic:\n virtual Document* createDocument() = 0; // 工厂方法\n virtual ~Application() = default;\n\n void newDocument() {\n Document* doc = createDocument();\n doc->open();\n delete doc; // 实际代码中应使用智能指针\n }\n};\n\nclass PDFApp : public Application {\n Document* createDocument() override { return new PDFDocument(); }\n};\n\n// 使用\nPDFApp app;\napp.newDocument(); // 打开 PDF\n\n结构型模式:构建构造器
\n\n这些模式帮助将类和对象组织成更大的结构,同时保持灵活性和效率。
\n\n适配器模式
\n\n允许接口不兼容的对象协同工作。想象一下,你有一个旧库用于处理 XML,但新模块需要 JSON。适配器就是你的救星。
\n\n何时使用: