设计模式六大原则 & 常用设计模式及实现【八股】
设计模式
设计模式六大原则
面向对象设计的“六大原则”旨在帮助开发者构建高内聚、低耦合、易扩展和易维护的软件系统。它们通常由著名的 SOLID 原则加上迪米特法则组成:
1. 单一职责原则 (Single Responsibility Principle, SRP)
- 核心思想: 一个类(或模块、函数)只应该有一个引起它变化的原因。换句话说,它应该只负责一项唯一的职责。
- 目的: 降低类的复杂性,提高代码的可读性和可维护性。如果一个类承担了太多职责,修改其中一个功能时,就有可能意外破坏其他功能。
2. 开闭原则 (Open-Closed Principle, OCP)
- 核心思想: 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- 目的: 当需要增加新需求或新功能时,应该尽量通过编写新代码(例如添加子类或实现新接口)来扩展系统,而不是去修改那些已经正常运行的旧代码。这被认为是面向对象设计中最核心的原则。
3. 里氏替换原则 (Liskov Substitution Principle, LSP)
- 核心思想: 程序中的所有父类对象都必须能够被其子类对象完全替换,且程序的原有逻辑和行为不会发生任何改变。
- 目的: 确保继承体系的合理性与健壮性。如果子类在重写父类方法时改变了原有的契约或基本行为,就会导致多态的失效并引发潜在的 Bug。
4. 依赖倒置原则 (Dependency Inversion Principle, DIP)
- 核心思想: 高层模块不应该依赖于低层模块,两者都应该依赖于抽象(接口或抽象类)。抽象不应该依赖于细节(具体实现),细节应该依赖于抽象。
- 目的:倡导“面向接口编程”,而非面向实现编程。这样可以大大降低不同模块之间的耦合度。
5. 接口隔离原则 (Interface Segregation Principle, ISP)
- 核心思想: 客户端不应该被强迫依赖它不需要的方法。一个类对另一个类的依赖应该建立在最小的接口上。
- 目的: 避免出现臃肿的“胖接口”。将大而全的接口拆分为多个专门的、细化的接口,让实现类只需要实现它们真正需要的方法。
6. 迪米特法则 / 最少知识原则 (Law of Demeter, LoD / Least Knowledge Principle, LKP)
- 核心思想: 一个对象应该对其他对象有尽可能少的了解。通常被表述为“只与你的直接朋友交谈,不要跟陌生人说话”。
- 目的: 减少类之间的相互依赖(即降低耦合)。如果两个类不必要彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法,可以通过第三方转发这个调用。
- 只和直接朋友通信,别做链式打听(如
a.getB().getC().doSomething())。减少对外部细节的依赖,降低系统偶合度。
常见设计模式
1. 策略模式 (Strategy Pattern)
- 核心意图: 将“变化的部分”从固定的逻辑中抽取出来。它定义了一系列算法,把它们一个个封装起来,并且使它们可以互相替换。
- 解决的痛点: 告别冗长且难以维护的
if-else或switch-case语句。如果不使用策略模式,当系统中出现一种新的算法分支时,你必须去修改主干代码,这严重违反了开闭原则 (OCP)(对扩展开放,对修改关闭)。 - 适用场景:
- 电商系统中的多种促销打折算法(满减、打折、返现)。
- 网络编程中的多种路由选择算法(哈希取模、一致性哈希)。
- 数据处理中的多种压缩算法(Zip、Gzip、Snappy)。
- C++ 落地细节: 在 C++ 中,Context 类通常通过
std::unique_ptr<Strategy>来组合策略对象。这样既明确了 Context 对策略的独占所有权,又能利用 C++ 的多态(虚函数)在运行时动态执行不同的算法。
#include <iostream>
#include <memory>
// 1. 抽象策略接口
class Strategy {
public:
virtual ~Strategy() = default;
virtual void executeAlgorithm() const = 0;
};
// 2. 具体策略 A 和 B
class ConcreteStrategyA : public Strategy {
public:
void executeAlgorithm() const override {
std::cout << "Executing Strategy A (e.g., Random Load Balancing)\n";
}
};
class ConcreteStrategyB : public Strategy {
public:
void executeAlgorithm() const override {
std::cout << "Executing Strategy B (e.g., Round-Robin Load Balancing)\n";
}
};
// 3. 上下文类(Context),持有一个策略对象的引用或指针
class Context {
private:
std::unique_ptr<Strategy> strategy_; // 使用智能指针管理生命周期
public:
Context(std::unique_ptr<Strategy> strategy = nullptr) : strategy_(std::move(strategy)) {}
// 运行时动态切换策略
void set_strategy(std::unique_ptr<Strategy> strategy) {
strategy_ = std::move(strategy);
}
void doSomeBusinessLogic() const {
if (strategy_) {
strategy_->executeAlgorithm();
} else {
std::cout << "Context: No strategy set.\n";
}
}
};
/* 调用示例:
Context context(std::make_unique<ConcreteStrategyA>());
context.doSomeBusinessLogic();
context.set_strategy(std::make_unique<ConcreteStrategyB>());
context.doSomeBusinessLogic();
*/
2. 代理模式 (Proxy Pattern)
- 核心意图: 为其他对象提供一种代理,以 控制 对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
- 解决的痛点: 目标对象无法直接被访问(比如在另一台服务器上),或者直接访问的代价太高(比如需要极其复杂的初始化、或者需要严格的权限校验)。
- 适用场景:
- 远程代理 (Remote Proxy): 隐藏对象存在于不同地址空间的事实(比如 RPC 框架的客户端 Stub)。
- 虚拟代理 (Virtual Proxy): 懒加载,当真正需要时才去创建开销极大的对象(比如网页中图片的占位符)。
- 保护代理 (Protection Proxy): 在访问真实对象前进行权限拦截。
- C++ 落地细节: Proxy 类和 RealSubject 类必须实现同一个抽象接口。这使得代理对象完全可以“伪装”成真实对象,对调用方做到 100% 透明。
#include <iostream>
#include <memory>
// 1. 抽象主题接口
class Subject {
public:
virtual ~Subject() = default;
virtual void request() const = 0;
};
// 2. 真实主题(被代理的对象)
class RealSubject : public Subject {
public:
void request() const override {
std::cout << "RealSubject: Handling the actual request.\n";
}
};
// 3. 代理类
class Proxy : public Subject {
private:
std::unique_ptr<RealSubject> real_subject_;
bool checkAccess() const {
std::cout << "Proxy: Checking access prior to firing a real request.\n";
return true;
}
void logAccess() const {
std::cout << "Proxy: Logging the time of request.\n";
}
public:
Proxy() : real_subject_(std::make_unique<RealSubject>()) {}
void request() const override {
if (this->checkAccess()) {
real_subject_->request();
this->logAccess();
}
}
};
/* 调用示例:
Proxy proxy;
proxy.request(); // 客户端只和代理交互
*/
3. 观察者模式 (Observer Pattern)
- 核心意图: 定义对象间的一种“一对多”的依赖关系。当一个对象(Subject)的状态发生改变时,所有依赖于它的对象(Observers)都会得到通知并自动更新。
- 解决的痛点: 避免了各个模块为了获取状态更新而进行极其耗费 CPU 的“轮询(Polling)”,实现了事件的“推(Push)”模型。
- 适用场景:
- UI 框架中的事件监听(点击按钮触发多个回调)。
- 发布-订阅系统(Pub/Sub)、消息队列(如 Kafka、RabbitMQ 的基础思想)。
- 微服务架构中的配置中心热更新。
- C++ 落地细节与坑点: Subject 在维护 Observer 列表时,如果使用裸指针或
std::shared_ptr,极易造成悬垂指针(Observer 被销毁了但 Subject 还在给它发消息)或 循环引用 导致内存泄漏。现代 C++ 最佳实践是使用std::weak_ptr,在通知前先通过.lock()检查 Observer 是否还存活。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <memory>
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string &message) = 0;
};
class ConcreteObserver : public Observer {
public:
ConcreteObserver(const std::string &name) : name_(name) {}
void update(const std::string &message) override {
std::cout << "Observer " << name_ << " received update: " << message << " \n";
}
private:
std::string name_;
};
class Subject {
public:
void attach(const std::shared_ptr<Observer> &observer) {
observers_.push_back(observer);
}
void detach(const std::shared_ptr<Observer> &observer) {
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[&observer](const std::weak_ptr<Observer> &wp){
auto sp = wp.lock();
return !sp || sp.get() == observer.get();
}),
observers_.end()
);
}
// notify 的同时使用 erase-remove_if 自动清除已经失效的 weak_ptr
void notify(const std::string& message) {
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[&message](const std::weak_ptr<Observer> &wp){
if(auto sp = wp.lock()) {
sp->update(message);
return false; // 存活,保留
}
return true; // 已销毁,移除
}),
observers_.end()
);
}
private:
std::vector<std::weak_ptr<Observer>> observers_;
};
int main() {
Subject subject;
auto obs1 = std::make_shared<ConcreteObserver>("A");
auto obs2 = std::make_shared<ConcreteObserver>("B");
subject.attach(obs1);
subject.attach(obs2);
subject.notify("Server is going down!");
subject.detach(obs1);
subject.notify("Second notification!");
return 0;
}
在 C++ 中,根据元素值删除
vector中的元素,主要有两种标准做法,取决于你使用的 C++ 标准:1. C++20 及以上:最精简的
std::eraseC++20 引入了针对容器的全局擦除函数,极大地简化了代码,这也是目前最推荐的做法:
#include <vector> std::vector<int> vec = {1, 2, 3, 2, 4}; // 直接删除 vector 中所有值为 2 的元素 std::erase(vec, 2); // 执行后 vec 变成 {1, 3, 4}2. C++20 之前:经典的 Erase-Remove 惯用法 (Idiom)
如果你使用的是 C++11/14/17,标准做法是结合
<algorithm>头文件中的std::remove算法和vector自身的erase方法:#include <vector> #include <algorithm> std::vector<int> vec = {1, 2, 3, 2, 4}; // Erase-Remove 惯用法 vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());为什么旧版本这么复杂?(核心原理)
std::remove的工作: 它本身不能改变容器的大小。它只是遍历一遍,把不需要删除的元素(比如 1, 3, 4)覆盖到 vector 的头部,然后返回一个指向“有效数据末尾”的迭代器。此时 vector 后面的元素相当于“废弃的脏数据”。vec.erase的工作: 接收std::remove返回的迭代器作为起点,以vec.end()作为终点,把末尾这部分“废弃数据”真正地从容器中切除,并缩小 vector 的大小。
4. 单例模式 (Singleton Pattern)
- 核心意图: 确保一个类只有一个实例,并提供一个全局访问点。
- 解决的痛点: 避免系统中频繁创建和销毁那些只需全局存在一份的重量级资源,防止资源访问冲突。
- 适用场景:
- 日志打印器(Logger),防止多线程写文件错乱。
- 配置管理器(ConfigManager),只需启动时加载一次。
- 数据库连接池、线程池的管理器。
- C++ 落地细节与坑点: 传统的“懒汉式”单例(先判断指针是否为空再
new)在多线程下会发生竞态条件,需要加锁,甚至需要用到极其丑陋的“双重检查锁定(DCL)”。而在 C++11 之后,绝对推荐 Meyers' Singleton(利用局部静态变量),因为 C++11 标准明确规定了局部静态变量的初始化是线程安全的,编译器会在底层自动帮你加锁。
#include <iostream>
class Singleton {
public:
// 获取全局唯一实例的静态方法
static Singleton& getInstance() {
// C++11 保证这里的静态变量初始化是线程安全的
static Singleton instance;
return instance;
}
void doSomething() {
std::cout << "Singleton instance is working.\n";
}
// 禁用拷贝构造函数和赋值操作符,防止产生多个实例
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 如果需要,也可以禁用移动构造和移动赋值
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
// 构造函数私有化
Singleton() {
std::cout << "Singleton initialized.\n";
}
~Singleton() = default;
};
/* 调用示例:
Singleton::getInstance().doSomething();
*/
Meyers' Singleton 的核心是:
- 私有构造函数
- delete 拷贝构造和拷贝赋值
- 公开静态方法,返回一个局部静态对象的引用
5. 工厂方法模式 (Factory Method Pattern)
- 核心意图: 将对象的创建逻辑推迟到子类中进行。定义一个创建对象的接口,但不指定具体的类。
- 解决的痛点: 消除代码中大量直接
new具体类的硬编码。当系统需要新增一种产品时,只需增加对应的具体产品类和对应的具体工厂类,不需要修改原有代码。 - 适用场景:
- 跨平台 UI 框架(WindowsFactory 创建 WindowsButton,MacFactory 创建 MacButton)。
- 日志系统中,不同的工厂创建不同的日志记录器(FileLogger、ConsoleLogger)。
- C++ 落地细节: 现代 C++ 的工厂方法绝对不应该返回裸指针(
Product*),否则调用方经常会忘记delete导致内存泄漏。工厂方法应该统一返回std::unique_ptr<Product>,将生命周期的管理权明确移交给调用方。
#include <iostream>
#include <memory>
#include <string>
// 1. 抽象产品
class Product {
public:
virtual ~Product() = default;
virtual void use() const = 0;
};
// 2. 具体产品
class ConcreteProductA : public Product {
public:
void use() const override { std::cout << "Using Product A\n"; }
};
class ConcreteProductB : public Product {
public:
void use() const override { std::cout << "Using Product B\n"; }
};
// 3. 抽象工厂
class Creator {
public:
virtual ~Creator() = default;
// 工厂方法
virtual std::unique_ptr<Product> createProduct() const = 0;
};
// 4. 具体工厂
class ConcreteCreatorA : public Creator {
public:
std::unique_ptr<Product> createProduct() const override {
return std::make_unique<ConcreteProductA>();
}
};
class ConcreteCreatorB : public Creator {
public:
std::unique_ptr<Product> createProduct() const override {
return std::make_unique<ConcreteProductB>();
}
};
int main()
{
std::unique_ptr<Creator> creator = std::make_unique<ConcreteCreatorA>();
std::unique_ptr<Product> product = creator->createProduct();
product->use();
}
不过在实际工程中,如果单纯为了创建对象,定义如此多的工厂子类往往过重。现代 C++ 常用 函数对象(std::function)+ 注册表 来扁平化工厂方法:
#include <unordered_map>
#include <functional>
class SimpleFactory {
public:
using CreatorFunc = std::function<std::unique_ptr<Product>()>;
static void registerProduct(const std::string& type, CreatorFunc func) {
getRegistry()[type] = std::move(func);
}
static std::unique_ptr<Product> create(const std::string& type) {
auto it = getRegistry().find(type);
return (it != getRegistry().end()) ? it->second() : nullptr;
}
private:
static auto getRegistry() -> std::unordered_map<std::string, CreatorFunc>& {
static std::unordered_map<std::string, CreatorFunc> registry;
return registry;
}
};
// 注册直接绑定 lambda,无需派生 Creator 子类:
// SimpleFactory::registerProduct("A", [] { return std::make_unique<ConcreteProductA>(); });
6. 责任链模式 (Chain of Responsibility Pattern)
- 核心意图: 将请求的处理者连成一条链,让请求沿着这条链传递,直到有对象处理它为止。
- 解决的痛点: 请求的发送者不需要知道到底是谁处理了请求,解耦了发送者和接收者。同时,你可以随时在链条中动态添加、移除或调整处理节点的顺序。
- 适用场景:
- Web 服务器的中间件(Middleware)架构(例如:CORS 拦截 -> 鉴权拦截 -> 限流拦截 -> 业务处理)。
- 办公系统中的审批流(组长审批 -> 经理审批 -> CEO 审批)。
- C++ 落地细节: 通常基类中会持有一个
std::shared_ptr<Handler> next_handler。在实现handleRequest时,一定要先处理自身的逻辑,再显式调用next_handler->handleRequest()。如果不小心忘记调用,整个链条就会断裂,请求就会莫名其妙地丢失。
#include <iostream>
#include <memory>
#include <string>
// 1. 抽象处理者
class Handler {
protected:
std::shared_ptr<Handler> next_handler_; // 指向下一个处理者
public:
virtual ~Handler() = default;
void setNext(std::shared_ptr<Handler> handler) {
next_handler_ = handler;
}
virtual void handleRequest(const std::string& request) {
if (next_handler_) {
next_handler_->handleRequest(request);
} else {
std::cout << "End of chain, request not handled.\n";
}
}
};
// 2. 具体处理者 A(比如:负责权限校验)
class AuthHandler : public Handler {
public:
void handleRequest(const std::string& request) override {
if (request == "AuthError") {
std::cout << "AuthHandler: Blocked request due to auth error.\n";
// 阻断链条,不再向后传递
} else {
std::cout << "AuthHandler: Passed.\n";
Handler::handleRequest(request); // 传递给下一个节点
}
}
};
// 3. 具体处理者 B(比如:负责业务逻辑)
class BusinessHandler : public Handler {
public:
void handleRequest(const std::string& request) override {
if(request == "StoreDataRequest") {
std::cout << "BusinessHandler: Passed.\n";
Handler::handleRequest(request);
}
else {
std::cout << "BusinessHandler: Handled the business logic for " << request << ".\n";
}
}
};
// 4. 具体处理者 C(比如:负责写入数据库)
class DatabaseHandler : public Handler {
public:
void handleRequest(const std::string& request) override {
std::cout << "DatabaseHandler: store data " << request << ".\n";
}
};
int main() {
auto auth = std::make_shared<AuthHandler>();
auto business = std::make_shared<BusinessHandler>();
auto database = std::make_shared<DatabaseHandler>();
auth->setNext(business); // 组装责任链
auth->handleRequest("NormalRequest"); // 会依次经过 Auth 和 Business
auth->handleRequest("AuthError"); // 会在 Auth 处被拦截
std::cout << std::endl;
business->setNext(database);
auth->handleRequest("StoreDataRequest");
}

浙公网安备 33010602011771号