设计模式六大原则 & 常用设计模式及实现【八股】

设计模式

设计模式六大原则

面向对象设计的“六大原则”旨在帮助开发者构建高内聚、低耦合、易扩展和易维护的软件系统。它们通常由著名的 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-elseswitch-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::erase

C++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");
}

posted @ 2026-09-08 15:11  光風霽月  阅读(4)  评论(0)    收藏  举报