设计模式学习笔记:理解与 C++ 示例
一组经过验证的“组织代码的经验”:当对象之间的关系开始变复杂时,设计模式帮助我们降低耦合、隔离变化、复用行为。
一、创建型模式
创建型模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。关注“对象怎么创建”。当直接 new 对象会让业务代码依赖具体类,或者创建流程比较复杂时,可以考虑创建型模式。
1. 单例模式 Singleton
单例模式保证一个类在程序中只有一个实例,并提供一个全局访问点。常见场景有日志器、配置中心、线程池管理器等。
单例的核心不是“全局变量”,而是“唯一资源”。如果某个对象代表全局唯一状态,就可以考虑单例。但它也容易隐藏依赖关系,所以不要滥用。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream>
class Logger { public: static Logger& instance() { static Logger logger; return logger; }
void log(const std::string& message) { std::cout << "[log] " << message << std::endl; }
private: Logger() = default; Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; };
int main() { Logger::instance().log("system started"); }
|
2. 简单工厂 Simple Factory
简单工厂不属于 GoF 23 种模式,但很常用。它把对象创建逻辑集中到一个工厂函数中,根据参数返回不同类型的对象。
简单工厂适合类型较少、变化不频繁的场景。它能把 new 从业务代码里拿走,但新增类型时通常要修改工厂函数。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream> #include <memory>
class Shape { public: virtual void draw() = 0; virtual ~Shape() = default; };
class Circle : public Shape { public: void draw() override { std::cout << "draw circle\n"; } };
class Rectangle : public Shape { public: void draw() override { std::cout << "draw rectangle\n"; } };
class ShapeFactory { public: static std::unique_ptr<Shape> create(const std::string& type) { if (type == "circle") return std::make_unique<Circle>(); if (type == "rectangle") return std::make_unique<Rectangle>(); return nullptr; } };
int main() { auto shape = ShapeFactory::create("circle"); shape->draw(); }
|
3. 工厂方法 Factory Method
工厂方法把对象创建延迟到子类中。父类定义创建接口,子类决定具体创建哪一种产品。
简单工厂是“一个工厂判断类型”,工厂方法是“不同工厂负责不同产品”。当新增产品时,可以新增工厂类,减少对原有工厂的修改。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <memory>
class Button { public: virtual void render() = 0; virtual ~Button() = default; };
class WindowsButton : public Button { public: void render() override { std::cout << "Windows button\n"; } };
class MacButton : public Button { public: void render() override { std::cout << "Mac button\n"; } };
class Dialog { public: virtual std::unique_ptr<Button> createButton() = 0; virtual ~Dialog() = default; };
class WindowsDialog : public Dialog { public: std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); } };
class MacDialog : public Dialog { public: std::unique_ptr<Button> createButton() override { return std::make_unique<MacButton>(); } };
int main() { std::unique_ptr<Dialog> dialog = std::make_unique<WindowsDialog>(); auto button = dialog->createButton(); button->render(); }
|
- 抽象工厂 Abstract Factory
抽象工厂用于创建一组相关对象。它提供多个创建接口,每个具体工厂负责创建同一产品族中的对象。
工厂方法关注“创建一个产品”,抽象工厂关注“创建一整套产品”。比如 Windows 风格的按钮和输入框应该成套出现,Mac 风格也应该成套出现。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream> #include <memory>
class Button { public: virtual void paint() = 0; virtual ~Button() = default; };
class Checkbox { public: virtual void paint() = 0; virtual ~Checkbox() = default; };
class WindowsButton : public Button { public: void paint() override { std::cout << "Windows button\n"; } };
class WindowsCheckbox : public Checkbox { public: void paint() override { std::cout << "Windows checkbox\n"; } };
class UIFactory { public: virtual std::unique_ptr<Button> createButton() = 0; virtual std::unique_ptr<Checkbox> createCheckbox() = 0; virtual ~UIFactory() = default; };
class WindowsFactory : public UIFactory { public: std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
std::unique_ptr<Checkbox> createCheckbox() override { return std::make_unique<WindowsCheckbox>(); } };
int main() { std::unique_ptr<UIFactory> factory = std::make_unique<WindowsFactory>(); factory->createButton()->paint(); factory->createCheckbox()->paint(); }
|
5. 建造者模式 Builder
建造者模式把复杂对象的构建过程拆成多个步骤,让同样的构建过程可以创建不同表示。
当构造函数参数太多,或者对象创建需要按步骤设置时,Builder 会比一个巨大构造函数更清晰。它尤其适合配置类、SQL 构造器、HTTP 请求构造器。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <iostream> #include <string>
class Computer { public: std::string cpu; std::string memory; std::string disk;
void show() const { std::cout << cpu << ", " << memory << ", " << disk << std::endl; } };
class ComputerBuilder { public: ComputerBuilder& setCpu(const std::string& cpu) { computer.cpu = cpu; return *this; }
ComputerBuilder& setMemory(const std::string& memory) { computer.memory = memory; return *this; }
ComputerBuilder& setDisk(const std::string& disk) { computer.disk = disk; return *this; }
Computer build() { return computer; }
private: Computer computer; };
int main() { Computer pc = ComputerBuilder() .setCpu("i7") .setMemory("16GB") .setDisk("1TB SSD") .build();
pc.show(); }
|
6. 原型模式 Prototype
原型模式通过复制已有对象来创建新对象,而不是从头构造。
如果对象创建成本高,或者运行时才知道具体类型,原型模式很有用。C++ 中通常用虚函数 clone() 实现多态复制。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <memory>
class Enemy { public: virtual std::unique_ptr<Enemy> clone() const = 0; virtual void attack() const = 0; virtual ~Enemy() = default; };
class RobotEnemy : public Enemy { public: std::unique_ptr<Enemy> clone() const override { return std::make_unique<RobotEnemy>(*this); }
void attack() const override { std::cout << "Robot enemy attacks\n"; } };
int main() { std::unique_ptr<Enemy> prototype = std::make_unique<RobotEnemy>(); auto enemy = prototype->clone(); enemy->attack(); }
|
二、结构型模式
结构型模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。
7. 适配器模式 Adapter
适配器模式把一个类的接口转换成客户端期望的另一个接口。
适配器像一个转换插头。老代码、第三方库、历史接口不方便改时,可以写一个适配器,让新代码按统一接口使用它。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <iostream>
class OldPrinter { public: void oldPrint() { std::cout << "print by old printer\n"; } };
class Printer { public: virtual void print() = 0; virtual ~Printer() = default; };
class PrinterAdapter : public Printer { public: explicit PrinterAdapter(OldPrinter& oldPrinter) : oldPrinter(oldPrinter) {}
void print() override { oldPrinter.oldPrint(); }
private: OldPrinter& oldPrinter; };
int main() { OldPrinter oldPrinter; PrinterAdapter adapter(oldPrinter); adapter.print(); }
|
8. 桥接模式 Bridge
桥接模式把抽象部分和实现部分分离,让它们可以独立变化。
当一个类有两个变化维度时,不要用继承堆出一大堆组合类。桥接模式把其中一个维度抽出去,用组合代替继承。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream> #include <memory>
class Renderer { public: virtual void renderCircle() = 0; virtual ~Renderer() = default; };
class VectorRenderer : public Renderer { public: void renderCircle() override { std::cout << "draw circle by vector\n"; } };
class Shape { public: explicit Shape(std::unique_ptr<Renderer> renderer) : renderer(std::move(renderer)) {}
virtual void draw() = 0; virtual ~Shape() = default;
protected: std::unique_ptr<Renderer> renderer; };
class Circle : public Shape { public: using Shape::Shape;
void draw() override { renderer->renderCircle(); } };
int main() { Circle circle(std::make_unique<VectorRenderer>()); circle.draw(); }
|
9. 组合模式 Composite
组合模式把对象组织成树形结构,使客户端可以用一致方式处理单个对象和对象集合。
组合模式适合“部分-整体”结构,比如文件系统、组织架构、UI 组件树。关键是让叶子节点和容器节点拥有同一个接口。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <memory> #include <vector>
class FileSystemNode { public: virtual void show() = 0; virtual ~FileSystemNode() = default; };
class File : public FileSystemNode { public: explicit File(std::string name) : name(std::move(name)) {}
void show() override { std::cout << "file: " << name << std::endl; }
private: std::string name; };
class Folder : public FileSystemNode { public: void add(std::unique_ptr<FileSystemNode> node) { children.push_back(std::move(node)); }
void show() override { for (auto& child : children) { child->show(); } }
private: std::vector<std::unique_ptr<FileSystemNode>> children; };
int main() { Folder folder; folder.add(std::make_unique<File>("main.cpp")); folder.add(std::make_unique<File>("README.md")); folder.show(); }
|
10. 装饰器模式 Decorator
装饰器模式在不修改原类的情况下,动态地给对象增加功能。
装饰器本质是“套娃式增强”。它和继承都能扩展功能,但装饰器是在运行时组合行为,灵活性更高。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> #include <memory>
class Coffee { public: virtual double cost() const = 0; virtual ~Coffee() = default; };
class SimpleCoffee : public Coffee { public: double cost() const override { return 10.0; } };
class MilkDecorator : public Coffee { public: explicit MilkDecorator(std::unique_ptr<Coffee> coffee) : coffee(std::move(coffee)) {}
double cost() const override { return coffee->cost() + 2.0; }
private: std::unique_ptr<Coffee> coffee; };
int main() { std::unique_ptr<Coffee> coffee = std::make_unique<MilkDecorator>(std::make_unique<SimpleCoffee>());
std::cout << coffee->cost() << std::endl; }
|
11. 外观模式 Facade
外观模式为复杂子系统提供一个简单统一的入口。
外观不是为了隐藏所有细节,而是给常用流程提供一个更友好的 API。它能让调用方少知道一些内部模块的组合方式。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream>
class CPU { public: void start() { std::cout << "CPU start\n"; } };
class Memory { public: void load() { std::cout << "Memory load\n"; } };
class ComputerFacade { public: void start() { cpu.start(); memory.load(); std::cout << "Computer ready\n"; }
private: CPU cpu; Memory memory; };
int main() { ComputerFacade computer; computer.start(); }
|
12. 享元模式 Flyweight
享元模式通过共享对象来减少内存开销,适合大量对象拥有相同内部状态的场景。
享元的重点是区分内部状态和外部状态。可以共享的放到享元对象里,不可共享的由调用方传入。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream> #include <memory> #include <string> #include <unordered_map>
class TreeType { public: TreeType(std::string name, std::string color) : name(std::move(name)), color(std::move(color)) {}
void draw(int x, int y) const { std::cout << name << " " << color << " at " << x << ", " << y << std::endl; }
private: std::string name; std::string color; };
class TreeFactory { public: std::shared_ptr<TreeType> getTreeType(const std::string& name, const std::string& color) { std::string key = name + ":" + color; if (!types.count(key)) { types[key] = std::make_shared<TreeType>(name, color); } return types[key]; }
private: std::unordered_map<std::string, std::shared_ptr<TreeType>> types; };
int main() { TreeFactory factory; auto type = factory.getTreeType("pine", "green"); type->draw(10, 20); type->draw(30, 40); }
|
13. 代理模式 Proxy
代理模式为真实对象提供一个替身,用于控制访问、延迟加载、权限检查或缓存。
代理和装饰器都包了一层对象,但目的不同:装饰器偏向增强功能,代理偏向控制访问。
C++ 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <memory>
class Image { public: virtual void display() = 0; virtual ~Image() = default; };
class RealImage : public Image { public: explicit RealImage(std::string filename) : filename(std::move(filename)) { std::cout << "load " << this->filename << std::endl; }
void display() override { std::cout << "display " << filename << std::endl; }
private: std::string filename; };
class ImageProxy : public Image { public: explicit ImageProxy(std::string filename) : filename(std::move(filename)) {}
void display() override { if (!image) { image = std::make_unique<RealImage>(filename); } image->display(); }
private: std::string filename; std::unique_ptr<RealImage> image; };
int main() { ImageProxy image("photo.png"); image.display(); image.display(); }
|
三、行为型模式
行为型模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。