C++设计模式

单例模式

单例模式就是保证一个类仅有一个实例(对象),并且提供一个访问它的全局访问点。

  • 使用场景

    • 当你需要控制某些共享资源,或者需要一个全局唯一的管理者时。
      • 日志打印
      • 配置文件管理器
  • 实现条件

    • 私有构造
    • 禁止拷贝和赋值构造
    • 提供一个静态方法
  • 实现方式

    • 饿汉模式:在程序启动(main 函数执行之前),单例对象就已经被创建并初始化好了。

      • 优点:天生线程安全。因为在多线程还没建立起来之前,对象就已经存在了,所以绝对不会出现两个线程同时去创建对象的情况。获取速度快,因为直接返回现成的。

      • 缺点:浪费资源。如果这个单例对象非常庞大(比如加载了几个G的字典文件),但你这次运行程序刚好没用到它,那它的创建就是纯纯的浪费内存和启动时间。

      c++ 复制代码
      #include <iostream>
      
      class EagerSingleton {
      public:
          // 获取实例的接口,直接返回已经做好的那盘"菜"
          static EagerSingleton* getInstance() {
              return instance;
          }
      
          void doSomething() {
              std::cout << "饿汉在工作..." << std::endl;
          }
      
          // 禁用拷贝和赋值
          EagerSingleton(const EagerSingleton&) = delete;
          EagerSingleton& operator=(const EagerSingleton&) = delete;
      
      private:
          // 构造函数私有化
          EagerSingleton() {
              std::cout << "-> 饿汉模式:程序还没进 main 函数,我就已经被创建了!" << std::endl;
          }
      
          // 声明一个静态指针(这是类级别的,不属于任何具体对象)
          static EagerSingleton* instance;
      };
      
      // 【关键点】:在类外立即初始化静态变量!
      // 这一步发生在程序启动时,main() 函数执行之前。
      EagerSingleton* EagerSingleton::instance = new EagerSingleton();
      
      
      // ================= 测试代码 =================
      int main() {
          std::cout << "--- 真正进入 main 函数了 ---" << std::endl;
          
          // 我们甚至还没开始用它,它就已经存在了
          EagerSingleton* s1 = EagerSingleton::getInstance();
          s1->doSomething();
          
          return 0;
      }
    • 懒汉模式:对象在第一次被使用时 (也就是第一次调用 getInstance 方法时)才会被动态创建。

      • 优点按需分配,节省资源。如果不使用,就永远不会被创建,不拖慢程序启动速度。

      • 缺点存在线程安全隐患。如果两个线程同时(并发)第一次来喊吃饭,他可能脑子一抽,在厨房做了两份饭(创建了两个实例),这就破坏了单例规则。所以传统的懒汉模式需要加锁(性能开销),或者使用现代C++的局部静态变量(像上一个Demo那样)。

      c++ 复制代码
      #include <iostream>
      #include <mutex> // 用于加锁保证线程安全
      
      class LazySingleton {
      public:
          // 获取实例的接口:喊他吃饭了
          static LazySingleton* getInstance() {
              // 【关键点】:先检查是不是还没做饭(是不是空指针)
              if (instance == nullptr) {
                  // 传统懒汉在多线程下不安全,这里通常需要加锁 
                  std::lock_guard<std::mutex> lck(_mutex);
                  if (instance == nullptr) { // 双重检查锁定 (Double-Checked Locking)
                      instance = new LazySingleton(); // 现做!
                  }
              }
              return instance;
          }
      
          void doSomething() {
              std::cout << "懒汉在工作..." << std::endl;
          }
      
          // 禁用拷贝和赋值
          LazySingleton(const LazySingleton&) = delete;
          LazySingleton& operator=(const LazySingleton&) = delete;
      
      private:
          LazySingleton() {
              std::cout << "-> 懒汉模式:你喊我了,我才刚被创建!" << std::endl;
          }
      
          static LazySingleton* instance;
          std::mutex _mutex;
      };
      
      // 【关键点】:类外初始化为 nullptr(空指针),这时候不创建对象!
      LazySingleton* LazySingleton::instance = nullptr;
      
      
      // ================= 测试代码 =================
      int main() {
          std::cout << "--- 真正进入 main 函数了 ---" << std::endl;
          std::cout << "程序运行了一会儿..." << std::endl;
          
          // 直到这里,懒汉单例都还不存在。
          
          std::cout << "现在我要用它了:" << std::endl;
          LazySingleton* s1 = LazySingleton::getInstance(); // 此时才触发 new 操作
          s1->doSomething();
          
          return 0;
      }
      • 最经典、最安全、最推荐的:利用局部静态变量的初始化是天然线程安全的

        c++ 复制代码
        #include <iostream>
        #include <string>
        
        // 单例类:配置管理器
        class ConfigManager {
        public:
            // 3. 提供一个全局静态方法,获取唯一实例
            static ConfigManager& getInstance() {
                // 这里的局部静态变量 instance 在第一次调用时被初始化
                // C++11标准保证了这里的初始化是线程安全的!
                static ConfigManager instance; 
                return instance;
            }
        
            // --- 以下是业务方法 ---
            void setServerIP(const std::string& ip) {
                serverIP = ip;
            }
        
            std::string getServerIP() const {
                return serverIP;
            }
        
            // 2. 禁用拷贝构造函数和赋值操作符(防止克隆!)
            ConfigManager(const ConfigManager&) = delete;
            ConfigManager& operator=(const ConfigManager&) = delete;
        
        private:
            // 1. 构造函数私有化(外部无法 new)
            ConfigManager() {
                std::cout << "ConfigManager 实例被创建了!(这行字应该只会出现一次)" << std::endl;
                serverIP = "127.0.0.1"; // 默认IP
            }
        
            // 析构函数也私有化或保持默认,防止被外部 delete
            ~ConfigManager() {
                std::cout << "ConfigManager 实例被销毁了!" << std::endl;
            }
        
            // 内部数据
            std::string serverIP;
        };
        
        // ================= 测试代码 =================
        int main() {
            std::cout << "--- 程序开始 ---" << std::endl;
        
            // 错误示范1:外部尝试直接创建会报错(编译不通过)
            // ConfigManager config; // ❌ 错误:构造函数是私有的
            // ConfigManager* pConfig = new ConfigManager(); // ❌ 错误:构造函数是私有的
        
            // 正确示范:通过 getInstance() 获取实例
            std::cout << "第一次获取实例:" << std::endl;
            ConfigManager& config1 = ConfigManager::getInstance();
            std::cout << "默认 IP: " << config1.getServerIP() << std::endl;
        
            // 修改配置
            std::cout << "\n修改 IP 为 192.168.1.100..." << std::endl;
            config1.setServerIP("192.168.1.100");
        
            // 第二次获取实例
            std::cout << "\n第二次获取实例:" << std::endl;
            ConfigManager& config2 = ConfigManager::getInstance();
            
            // 验证:看看 config2 里的 IP 是不是刚才 config1 修改后的
            std::cout << "当前 IP: " << config2.getServerIP() << std::endl;
        
            // 验证:看看它们的内存地址是否完全一样
            std::cout << "\nconfig1 的内存地址: " << &config1 << std::endl;
            std::cout << "config2 的内存地址: " << &config2 << std::endl;
            
            if (&config1 == &config2) {
                std::cout << "-> 结论:它们是同一个实例!单例模式生效。" << std::endl;
            }
        
            // 错误示范2:尝试克隆会报错(编译不通过)
            // ConfigManager config3 = config1; // ❌ 错误:拷贝构造函数已被 delete
            // ConfigManager config4;
            // config4 = config1; // ❌ 错误:赋值操作符已被 delete
        
            std::cout << "--- 程序结束 ---" << std::endl;
            return 0;
        }

工厂模式

把"对象的创建"和"对象的使用"分离开来,通俗解释:你直接找到一家"代工厂",对厂长说:"给我来一部 iPhone"。厂长负责在后台造好,直接把成品手机递给你。你根本不需要知道手机是怎么造出来的。

  • 简单工厂

    这是最基础的形态。整个系统里只有一个万能的工厂 ,你给它传一个参数(比如名字或枚举),它通过内部的 if-elseswitch 语句来决定造什么产品给你。

    • 优点:简单粗暴,小白也能看懂。

    • 缺点 :不符合"开闭原则(Open-Closed Principle)"。每次你想增加一个新品牌(比如华为),你都必须扒开这个万能工厂内部的代码,强行往里面加一个 else if

    c++ 复制代码
    #include <iostream>
    #include <string>
    #include <memory>
    
    // 1. 定义产品的抽象接口(所有手机必须实现的功能)
    class Phone {
    public:
        virtual ~Phone() = default;
        virtual void call() = 0; // 纯虚函数
    };
    
    // 2. 具体产品类
    class IPhone : public Phone {
    public:
        void call() override { std::cout << "用 iPhone 打电话:Siri 帮我拨号..." << std::endl; }
    };
    
    class XiaomiPhone : public Phone {
    public:
        void call() override { std::cout << "用 小米 打电话:小爱同学 帮我拨号..." << std::endl; }
    };
    
    // 3. 万能的简单工厂
    class SimpleFactory {
    public:
        // 根据传入的类型,返回具体的智能指针对象
        static std::unique_ptr<Phone> createPhone(const std::string& type) {
            if (type == "Apple") {
                return std::make_unique<IPhone>();
            } else if (type == "Xiaomi") {
                return std::make_unique<XiaomiPhone>();
            }
            return nullptr;
        }
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 简单工厂演示 ---" << std::endl;
        // 客户端完全不需要 new IPhone(),直接找工厂要
        auto myPhone1 = SimpleFactory::createPhone("Apple");
        if (myPhone1) myPhone1->call();
    
        auto myPhone2 = SimpleFactory::createPhone("Xiaomi");
        if (myPhone2) myPhone2->call();
    
        return 0;
    }
  • 工厂方法

    为了解决简单工厂"每次加新产品都要修改万能工厂代码"的痛点,工厂方法模式诞生了。 核心思想 :把工厂也抽象化!我们不再用一个万能工厂,而是为每一种产品配备一个专属的工厂

    • 打个比方:现在我们有"苹果专属代工厂"和"小米专属代工厂"。你想买苹果,就去找苹果代工厂;想买小米,就去找小米代工厂。

    • 优点 :完美符合"开闭原则"。以后要是想加"华为手机",你只需要写一个 HuaweiPhone 类和一个 HuaweiFactory 类就行了,原有的任何代码都不用修改

    • 缺点:类太多了。每加一个产品,就要配套加一个工厂类,容易造成"类爆炸"。

    c++ 复制代码
    // 1. 抽象工厂接口(定义所有专属工厂必须具备的造手机能力)
    class AbstractPhoneFactory {
    public:
        virtual ~AbstractPhoneFactory() = default;
        virtual std::unique_ptr<Phone> createPhone() = 0; 
    };
    
    // 2. 具体的专属工厂
    class AppleFactory : public AbstractPhoneFactory {
    public:
        std::unique_ptr<Phone> createPhone() override {
            return std::make_unique<IPhone>();
        }
    };
    
    class XiaomiFactory : public AbstractPhoneFactory {
    public:
        std::unique_ptr<Phone> createPhone() override {
            return std::make_unique<XiaomiPhone>();
        }
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 工厂方法演示 ---" << std::endl;
        
        // 我想要苹果手机,所以我先找到苹果代工厂
        std::unique_ptr<AbstractPhoneFactory> appleFactory = std::make_unique<AppleFactory>();
        auto myPhone = appleFactory->createPhone();
        myPhone->call();
    
        // 如果后续想造华为,完全不用改上面的代码,自己扩展即可
        return 0;
    }
  • 抽象工厂模式

    这是工厂模式的最终形态。它解决的是**"产品族(一系列相关的产品)"的问题。 核心思想:刚才的工厂只能造 单一产品**(手机)。但现实中,苹果代工厂不仅造 iPhone,还造 MacBook(电脑);小米不仅造手机,还造小米笔记本。为了保证买到的手机和电脑是同一个生态/同一家厂的,我们需要抽象工厂。

    • 打个比方:抽象工厂是一个大型综合代工厂的图纸。它规定了这个厂必须能同时造"手机"和"电脑"。苹果厂和小米厂按照这个图纸各自建厂。

    • 优点:保证客户端始终只使用同一个产品族中的对象(用苹果手机就配苹果电脑,不会串味)。

    • 缺点 :很难增加新的产品种类。比如你突然要求工厂不仅造手机和电脑,还要造"智能手表",那你得把所有的接口和所有的具体工厂类全修改一遍。

    c++ 复制代码
    #include <iostream>
    #include <memory>
    
    // --- 第一部分:定义所有的产品接口 ---
    class Phone {
    public:
        virtual ~Phone() = default;
        virtual void call() = 0;
    };
    
    class Laptop { // 新增产品:电脑
    public:
        virtual ~Laptop() = default;
        virtual void code() = 0;
    };
    
    // --- 第二部分:定义具体的产品 ---
    class IPhone : public Phone {
    public: void call() override { std::cout << "iPhone 拨号中..." << std::endl; }
    };
    class MacBook : public Laptop {
    public: void code() override { std::cout << "MacBook 写C++代码中..." << std::endl; }
    };
    
    class XiaomiPhone : public Phone {
    public: void call() override { std::cout << "小米手机 拨号中..." << std::endl; }
    };
    class MiLaptop : public Laptop {
    public: void code() override { std::cout << "小米电脑 写C++代码中..." << std::endl; }
    };
    
    // --- 第三部分:定义抽象工厂(规定必须能造手机和电脑) ---
    class DigitalFactory {
    public:
        virtual ~DigitalFactory() = default;
        virtual std::unique_ptr<Phone> createPhone() = 0;
        virtual std::unique_ptr<Laptop> createLaptop() = 0;
    };
    
    // --- 第四部分:具体的品牌工厂(实现产品族) ---
    class AppleDigitalFactory : public DigitalFactory {
    public:
        std::unique_ptr<Phone> createPhone() override { return std::make_unique<IPhone>(); }
        std::unique_ptr<Laptop> createLaptop() override { return std::make_unique<MacBook>(); }
    };
    
    class XiaomiDigitalFactory : public DigitalFactory {
    public:
        std::unique_ptr<Phone> createPhone() override { return std::make_unique<XiaomiPhone>(); }
        std::unique_ptr<Laptop> createLaptop() override { return std::make_unique<MiLaptop>(); }
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 抽象工厂演示 ---" << std::endl;
        
        // 客户说:我要一套苹果全家桶!
        std::unique_ptr<DigitalFactory> factory = std::make_unique<AppleDigitalFactory>();
        
        // 工厂开始统一生产苹果生态的产品
        auto myPhone = factory->createPhone();
        auto myLaptop = factory->createLaptop();
        
        myPhone->call();
        myLaptop->code();
    
        return 0;
    }

建造者模式

允许你一步一步地定制出一个极其复杂的专属产品

  • 为什么需要

    假设我们要写一个 Computer 类,一台电脑有非常多的配件:CPU、主板、内存、硬盘、显卡、电源、机箱、散热器...... 如果你用传统的构造函数来创建对象,你的代码会变成这样:

    c++ 复制代码
    // 构造函数参数爆炸(这被称为"伸缩构造函数反模式")
    Computer myPC = Computer("i9-13900K", "Z790", "32G", "1TB", "RTX4090", "1000W", "海景房机箱", "水冷");
    • 参数太多了!稍微写错一个位置(比如把内存和硬盘传反了),编译器也查不出来,运行时直接崩溃。
    • 很多配件是可选的 。比如有些办公机不需要独立显卡,有些不需要水冷。为了应付这些情况,你得写无数个重载的构造函数,或者在参数里狂塞 nullptr"",代码极度丑陋。
  • 有了建造者模式后(现代做法):

    • 我们引入一个"装机老板(Builder)"。你不需要一次性把所有参数报出来,而是像点菜一样,一步步跟老板说你要什么。老板把你的需求记在小本本上,最后你喊一句"装机!",老板就把配置好的电脑交给你。

    c++ 复制代码
    #include <iostream>
    #include <string>
    #include <memory>
    
    // --- 1. 复杂的产品类:Computer ---
    class Computer {
    public:
        // 允许 Builder 访问私有成员(或者把构造函数设为 public,看具体需求,这里用友元更严谨)
        friend class ComputerBuilder; 
    
        void showConfiguration() const {
            std::cout << "=== 您的电脑配置清单 ===" << std::endl;
            std::cout << "CPU: " << cpu << std::endl;
            std::cout << "内存: " << ram << std::endl;
            std::cout << "存储: " << storage << std::endl;
            // 可选配件
            if (!gpu.empty()) std::cout << "显卡: " << gpu << std::endl;
            if (!cooler.empty()) std::cout << "散热: " << cooler << std::endl;
            std::cout << "========================" << std::endl;
        }
    
    private:
        // 构造函数私有化,强制要求通过 Builder 来创建
        Computer() = default; 
    
        // 必填和选填的属性
        std::string cpu;
        std::string ram;
        std::string storage;
        std::string gpu;    // 可选
        std::string cooler; // 可选
    };
    
    // --- 2. 建造者类:ComputerBuilder ---
    class ComputerBuilder {
    public:
        ComputerBuilder() {
            // 在开始建造时,先准备一个空的产品骨架
            computer = std::make_unique<Computer>();
        }
    
        // --- 以下是各个配件的"定制步骤" ---
        // 【关键点】:返回引用 ComputerBuilder&,以便支持链式调用
        
        ComputerBuilder& setCPU(const std::string& cpuModel) {
            computer->cpu = cpuModel;
            return *this; 
        }
    
        ComputerBuilder& setRAM(const std::string& ramSize) {
            computer->ram = ramSize;
            return *this;
        }
    
        ComputerBuilder& setStorage(const std::string& storageSize) {
            computer->storage = storageSize;
            return *this;
        }
    
        ComputerBuilder& setGPU(const std::string& gpuModel) {
            computer->gpu = gpuModel;
            return *this;
        }
    
        ComputerBuilder& setCooler(const std::string& coolerType) {
            computer->cooler = coolerType;
            return *this;
        }
    
        // --- 3. 最终交付步骤:build() ---
        // 调用这个方法,表示组装完毕,把产品交出去
        std::unique_ptr<Computer> build() {
            // 可以在这里加上校验逻辑,比如:"没有装CPU就不准出货!"
            if (computer->cpu.empty() || computer->ram.empty()) {
                throw std::runtime_error("组装失败:CPU和内存是必填项!");
            }
            
            // 使用 std::move 把所有权转移给调用者
            return std::move(computer); 
        }
    
    private:
        std::unique_ptr<Computer> computer;
    };
    
    
    // ================= 测试代码 =================
    int main() {
        try {
            std::cout << "--- 客户 A:我要配一台顶级游戏主机! ---" << std::endl;
            // 链式调用,一气呵成,可读性极强!而且不在乎调用顺序
            std::unique_ptr<Computer> gamingPC = ComputerBuilder()
                .setCPU("Intel i9-13900K")
                .setGPU("NVIDIA RTX 4090") // 先装显卡再装内存也没关系
                .setRAM("64GB DDR5")
                .setStorage("4TB SSD")
                .setCooler("360水冷")
                .build(); // 最后点击组装
                
            gamingPC->showConfiguration();
    
            std::cout << "\n--- 客户 B:我只要一台简单的办公机,不要显卡 ---" << std::endl;
            // 缺省了 GPU 和 Cooler,也不会有几万个构造函数重载的问题
            std::unique_ptr<Computer> officePC = ComputerBuilder()
                .setCPU("Intel i5-13400")
                .setRAM("16GB DDR4")
                .setStorage("512GB SSD")
                .build();
                
            officePC->showConfiguration();
    
        } catch (const std::exception& e) {
            std::cerr << "报错了: " << e.what() << std::endl;
        }
    
        return 0;
    }
  • 工厂模式 vs 建造者模式

特点 工厂模式 (Factory) 建造者模式 (Builder)
关注点 "造什么"(不管过程,直接要成品)。 "怎么造"(关注一步步组装的细节和个性化配置)。
现实比喻 去买一台组装好的品牌机(如 联想拯救者),不需要你知道里面有啥。 去电脑城DIY组装机,由你亲自决定挑哪款主板、哪款显卡。
代码表现 传一个参数进去,直接 return 一个完整的对象。 调用很多个 setXXX() 方法,最后调用 build() 才得到对象。

迭代器模式

迭代器模式就是提供一种方法,让你能够顺序访问一个"集合(比如数组、链表)"里面的各个元素,又不需要暴露这个集合底层的内部结构。

c++ 复制代码
#include <iostream>
#include <string>

// 1. 数据载体:书本
struct Book {
    std::string name;
};

// 2. 自定义集合:书架
class Bookshelf {
public:
    Bookshelf() : count(0) {}

    // 添加书籍
    void addBook(const std::string& name) {
        if (count < 100) {
            books[count].name = name;
            count++;
        }
    }

    // ==========================================
    // 核心开始:内部定义一个迭代器类
    // 它的作用就是充当一个"聪明的指针",用来游走于书架之间
    // ==========================================
    class Iterator {
    public:
        // 构造函数:接收一个指向当前书本的普通指针
        Iterator(Book* ptr) : current_ptr(ptr) {}

        // 必须重载的运算符 1:解引用 (*),获取当前指着的书本
        Book& operator*() {
            return *current_ptr;
        }

        // 必须重载的运算符 2:自增 (++),走到下一本书
        Iterator& operator++() {
            current_ptr++; // 指针往后挪一个位置
            return *this;
        }

        // 必须重载的运算符 3:不等于 (!=),判断是否已经遍历到底了
        bool operator!=(const Iterator& other) const {
            return current_ptr != other.current_ptr;
        }

    private:
        Book* current_ptr; // 迭代器内部藏着一个真正的游标(指针)
    };

    // ==========================================
    // 为书架提供 begin() 和 end() 接口
    // ==========================================
    
    // 指向第一本书
    Iterator begin() {
        return Iterator(&books[0]); 
    }

    // 指向最后一本书的【下一个位置】(表示越界/结束)
    Iterator end() {
        return Iterator(&books[count]); 
    }

private:
    Book books[100]; // 底层数据结构:固定大小的普通数组
    int count;       // 当前存了多少本书
};

// ================= 测试代码 =================
int main() {
    std::cout << "--- 迭代器模式演示 ---" << std::endl;

    // 1. 创建一个书架并塞入几本书
    Bookshelf myShelf;
    myShelf.addBook("《C++ Primer》");
    myShelf.addBook("《Effective C++》");
    myShelf.addBook("《设计模式》");

    std::cout << "\n方式一:使用我们自定义的迭代器手动遍历" << std::endl;
    // 获取迭代器的起点和终点
    Bookshelf::Iterator it = myShelf.begin();
    Bookshelf::Iterator end = myShelf.end();
    
    // 只要没到终点,就一直往后走
    while (it != end) {
        std::cout << "阅读: " << (*it).name << std::endl;
        ++it; // 按下"下一首"按钮
    }

    std::cout << "\n方式二:C++11 语法糖(范围 for 循环)" << std::endl;
    // 【魔法时刻】:因为我们实现了 begin, end, *, ++, != 
    // 编译器会自动把下面这行极度简洁的代码,翻译成上面"方式一"的复杂样子!
    for (auto& book : myShelf) {
        std::cout << "重温: " << book.name << std::endl;
    }

    return 0;
}
  • 优点

    • 信息隐藏 :客户端(main 函数)里,你根本看不到 Book books[100] 这个底层数组。以后哪天如果你嫌数组不好用,把底层的 books 换成了链表,main 函数里的遍历代码连一个标点符号都不用改! 这就叫解耦。
    • 职责分离Bookshelf 这个类只负责"怎么存书"(增删查),而 Iterator 这个类只负责"怎么翻书"。各司其职,符合面向对象的单一职责原则。
    • 算法与数据的桥梁 :在 C++ 的 STL 中,你可以用同一个 std::sort 算法,去排序 vector,也可以去排序 deque。凭什么?就因为它们都提供了标准的迭代器。迭代器把各种千奇百怪的数据结构,统一变成了同一种访问方式。

适配器模式

当你希望复用一些现有的类(老代码、第三方库),但是它的接口(方法名、参数)跟你当前系统要求的接口不一致时,你就可以写一个适配器类(Adapter)。适配器负责把老接口"伪装"成新接口,让本来水火不容的两个类能够一起工作。

  • 实现方式:C++ 中的两种适配器:对象适配器 vs 类适配器

    • 对象适配器(组合模式)

      • 做法 :适配器类继承新接口,并在内部**包含(组合)**一个老对象的指针。
      • 优点:极其灵活,不仅能适配老类本身,还能适配老类的所有子类。符合"多用组合,少用继承"的金科玉律。
    • 类适配器(多重继承)

      • 做法 :适配器类同时多重继承新接口和老类(通常是 public 继承新接口,private 继承老类)。
      • 缺点:C++ 独有的花活,因为 Java 等语言不支持多继承。这种方式耦合度太高,且无法适配老类的子类,现代 C++ 开发中已经很少用了。
    c++ 复制代码
    #include <iostream>
    #include <memory>
    
    // ==========================================
    // 1. 目标接口 (Target):当前系统期待的新接口
    // 也就是我们的新手机,它只认 Type-C
    // ==========================================
    class TypeCPhone {
    public:
        virtual ~TypeCPhone() = default;
        
        // 手机规定:听歌必须调用这个 Type-C 方法
        virtual void listenMusicWithTypeC() = 0; 
    };
    
    // ==========================================
    // 2. 被适配者 (Adaptee):老代码 / 第三方库
    // 也就是我们的 3.5mm 老耳机,功能完美,但接口不对
    // ==========================================
    class Old35mmEarphone {
    public:
        // 老耳机的播放方法,名字和新手机要求的完全不同
        void plugInto35mmHole() {
            std::cout << "-> 3.5mm 经典耳机已接入,开始播放无损高保真音乐!🎵" << std::endl;
        }
    };
    
    // ==========================================
    // 3. 适配器 (Adapter):转接头登场!
    // 核心:实现新接口,内部藏着老对象
    // ==========================================
    class EarphoneAdapter : public TypeCPhone {
    public:
        // 构造函数:把老耳机插到转接头里
        EarphoneAdapter(std::shared_ptr<Old35mmEarphone> earphone) 
            : oldEarphone(earphone) {}
    
        // 实现新系统要求的方法
        void listenMusicWithTypeC() override {
            std::cout << "[转接头工作] 接收 Type-C 数字信号,转换为 3.5mm 模拟信号..." << std::endl;
            
            // 【关键魔法】:表面上调用的是新方法,实际上偷偷调用了老对象的老方法
            if (oldEarphone) {
                oldEarphone->plugInto35mmHole(); 
            }
        }
    
    private:
        // 内部"组合"了一个老对象的智能指针
        std::shared_ptr<Old35mmEarphone> oldEarphone; 
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 适配器模式演示 ---" << std::endl;
    
        // 1. 翻出我们落灰的、但是音质极好的老耳机
        auto myOldEarphone = std::make_shared<Old35mmEarphone>();
    
        // 注意:这个时候如果你想直接把 myOldEarphone 插进手机是做不到的,因为类型不匹配
    
        // 2. 买一个转接头,把老耳机插进去
        // 返回值是一个 TypeCPhone 的指针,意味着它现在伪装成了一个支持 Type-C 的设备
        std::unique_ptr<TypeCPhone> adapterDongle = std::make_unique<EarphoneAdapter>(myOldEarphone);
    
        // 3. 手机开机,开始听歌!
        // 手机(客户端)只管调用 Type-C 的方法,根本不知道背后其实是老耳机在出声
        std::cout << "\n手机:准备播放音乐..." << std::endl;
        adapterDongle->listenMusicWithTypeC();
    
        return 0;
    }

观察者模式

观察者模式定义了对象之间的一对多依赖关系。当**一个对象(UP主/发布者)的状态发生改变时,所有依赖于它的对象(粉丝/订阅者)**都会得到通知并被自动更新。

  • 为什么需要

    • 没有观察者模式时(轮询): 你非常喜欢某位 UP 主,你急切地想看他的新视频。于是你每天定闹钟,每隔一小时就去刷新他的个人主页,看看有没有更新。
      • 痛点:这种方式叫"轮询(Polling)"。你极度浪费时间(在代码中就是极度浪费 CPU 资源),而且 UP 主可能根本没更新。
    • 有了观察者模式后(事件驱动):你点了一下"关注并开启小铃铛" 。然后你就可以去干别的事了。当 UP 主发视频的那一刻,系统会自动给你,以及其他几百万粉丝的手机上弹出一条推送:"你关注的 UP 主更新啦!"。
      • 优点 :你不需要去盯着他了,状态一有变化,他会主动来通知你
  • 实现

    • Subject(主题 / 发布者):也就是 UP 主。他手里必须攥着一个**"粉丝名单(列表)"**。他需要提供三个基本功能:

      • 允许别人关注他(添加到名单)。
      • 允许别人取关(从名单移除)。
      • 群发通知(遍历名单,挨个通知)。
    • Observer(观察者 / 订阅者) :也就是粉丝。粉丝必须提供一个统一的接口,比如叫 update()。这样 UP 主才能通过这个统一的接口把消息传达给你。

    c++ 复制代码
    #include <iostream>
    #include <string>
    #include <list>
    
    // ==========================================
    // 1. 观察者接口 (Observer):所有粉丝必须遵守的规范
    // ==========================================
    class Observer {
    public:
        virtual ~Observer() = default;
        
        // 接收通知的统一方法
        virtual void update(const std::string& videoTitle) = 0; 
    };
    
    // ==========================================
    // 2. 主题接口 (Subject):UP主必须具备的功能
    // ==========================================
    class Subject {
    public:
        virtual ~Subject() = default;
        
        virtual void attach(Observer* observer) = 0; // 关注(加入粉丝群)
        virtual void detach(Observer* observer) = 0; // 取关(踢出粉丝群)
        virtual void notify(const std::string& videoTitle) = 0; // 发送群通知
    };
    
    // ==========================================
    // 3. 具体的观察者 (Concrete Observer):真实的粉丝
    // ==========================================
    class BilibiliFan : public Observer {
    public:
        BilibiliFan(const std::string& name) : fanName(name) {}
    
        // 粉丝收到通知后的具体反应
        void update(const std::string& videoTitle) override {
            std::cout << "[粉丝 " << fanName << " 收到推送]:哇!新视频《" 
                      << videoTitle << "》更新了,火速去一键三连!" << std::endl;
        }
    
    private:
        std::string fanName;
    };
    
    // ==========================================
    // 4. 具体的主题 (Concrete Subject):真实的UP主
    // ==========================================
    class UpHost : public Subject {
    public:
        // 粉丝点关注
        void attach(Observer* observer) override {
            fansList.push_back(observer);
        }
    
        // 粉丝取消关注
        void detach(Observer* observer) override {
            fansList.remove(observer);
        }
    
        // 内部方法:遍历粉丝列表,挨个调用他们的 update 方法
        void notify(const std::string& videoTitle) override {
            for (auto* fan : fansList) {
                fan->update(videoTitle);
            }
        }
    
        // UP主的实际业务逻辑:发布视频
        void publishVideo(const std::string& title) {
            std::cout << "\n【系统广播】UP主发布了全新视频:《" << title << "》" << std::endl;
            std::cout << "正在向所有粉丝发送推送通知..." << std::endl;
            
            // 视频发完,立刻触发通知机制
            notify(title); 
        }
    
    private:
        // 核心数据结构:用来存放所有关注了这个UP主的粉丝指针
        std::list<Observer*> fansList; 
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 观察者模式演示 ---" << std::endl;
    
        // 1. 创建一个 UP 主
        UpHost techUpHost;
    
        // 2. 创建三个粉丝
        BilibiliFan fan1("张三");
        BilibiliFan fan2("李四");
        BilibiliFan fan3("王五");
    
        // 3. 粉丝们开始关注 UP 主
        techUpHost.attach(&fan1);
        techUpHost.attach(&fan2);
        techUpHost.attach(&fan3);
    
        // 4. UP 主第一次发视频
        techUpHost.publishVideo("C++ 设计模式完全指南");
    
        // 5. 李四觉得视频太硬核,取关了
        std::cout << "\n[动态] 粉丝 李四 取消了关注。" << std::endl;
        techUpHost.detach(&fan2);
    
        // 6. UP 主第二次发视频 (此时李四应该收不到通知了)
        techUpHost.publishVideo("手撕红黑树源码");
    
        return 0;
    }
  • 优点:解耦

    • UP 主根本不在乎粉丝是谁 :他不需要知道张三是干嘛的,李四住在哪里。他只知道一件事:"只要在我的 fansList 里,并且有 update() 方法的人,我就把字符串塞给他"。
    • 动态的热插拔 :你可以在程序运行的任何时刻,动态地添加新的观察者,或者移除旧的观察者,而完全不需要修改 UP 主的核心代码

策略模式

  • 为什么要用

    • 假设你要写一个地图导航软件,用户输入起点和终点后,系统要规划路线。 一开始,你只有"驾车"功能。后来产品经理说要加"步行",再后来又要加"骑行"、"公交"...... 如果你不改变代码结构,你的 Navigator(导航器)类里的 buildRoute() 方法会变成这样:

      c++ 复制代码
      void buildRoute(起点, 终点, 出行方式) {
          if (出行方式 == "驾车") {
              // 避开拥堵、计算油耗、考虑限行... (几百行代码)
          } else if (出行方式 == "步行") {
              // 寻找人行道、抄近道、避开高速... (几百行代码)
          } else if (出行方式 == "公交") {
              // 查找公交站、计算换乘、预估步行时间... (几百行代码)
          }
      }

      痛点:这个类会无限膨胀(几千行代码)。只要修改其中一种导航逻辑,就极容易不小心把其他逻辑改出 Bug。每次增加新出行方式,都要修改这个核心类,严重违背了**"开闭原则"**。

    • 有了策略模式后:策略模式的思路是:把每一种"出行方式的算法"单独抽离出来,封装成独立的类(这叫"策略")。 导航软件本身(上下文)不再负责计算路线,它只负责把用户的起点和终点外包给具体的"策略类"去计算。

  • 实现

    • 分工

      • Strategy(策略接口) :所有具体算法必须遵守的统一规范(比如都必须有一个 calculateRoute 方法)。
      • Concrete Strategy(具体策略) :实现了上面接口的实体类,比如 DriveStrategy(驾车)、WalkStrategy(步行)。
      • Context(上下文):也就是使用策略的主体(导航软件)。它内部持有一个策略接口的指针,用户告诉它用哪个策略,它就去调用哪个。
    c++ 复制代码
    #include <iostream>
    #include <string>
    #include <memory>
    
    // ==========================================
    // 1. 策略接口 (Strategy):定义所有出行算法的通用规范
    // ==========================================
    class RouteStrategy {
    public:
        virtual ~RouteStrategy() = default;
        
        // 所有的策略都必须能规划路线
        virtual void buildRoute(const std::string& start, const std::string& end) = 0;
    };
    
    // ==========================================
    // 2. 具体策略 (Concrete Strategies):各种具体的出行算法
    // ==========================================
    class DriveStrategy : public RouteStrategy {
    public:
        void buildRoute(const std::string& start, const std::string& end) override {
            std::cout << "[驾车路线] 从 " << start << " 到 " << end 
                      << " -> 走高速,距离 20km,预计 30 分钟,注意避让行人。" << std::endl;
        }
    };
    
    class WalkStrategy : public RouteStrategy {
    public:
        void buildRoute(const std::string& start, const std::string& end) override {
            std::cout << "[步行路线] 从 " << start << " 到 " << end 
                      << " -> 走人行天桥和林荫小道,距离 2km,预计 25 分钟,就当锻炼身体。" << std::endl;
        }
    };
    
    class PublicTransitStrategy : public RouteStrategy {
    public:
        void buildRoute(const std::string& start, const std::string& end) override {
            std::cout << "[公交路线] 从 " << start << " 到 " << end 
                      << " -> 乘坐地铁 2 号线转 4 号线,预计 40 分钟,车费 5 元。" << std::endl;
        }
    };
    
    // ==========================================
    // 3. 上下文 (Context):导航软件
    // 它本身不计算路线,它只负责调用当前的策略
    // ==========================================
    class Navigator {
    public:
        // 动态设置/切换当前的出行策略
        void setStrategy(std::unique_ptr<RouteStrategy> newStrategy) {
            strategy = std::move(newStrategy);
        }
    
        // 执行核心业务:规划路线
        void executeRoutePlanning(const std::string& start, const std::string& end) {
            if (!strategy) {
                std::cout << "错误:请先选择出行方式(设置策略)!" << std::endl;
                return;
            }
            std::cout << "\n开始为您规划路线..." << std::endl;
            // 把具体工作委托给当前的策略对象
            strategy->buildRoute(start, end);
        }
    
    private:
        // 内部持有一个策略接口的智能指针
        std::unique_ptr<RouteStrategy> strategy;
    };
    
    // ================= 测试代码 =================
    int main() {
        std::cout << "--- 策略模式演示 ---" << std::endl;
    
        Navigator amap; // 打开高德地图
        std::string startPoint = "家里";
        std::string endPoint = "公司";
    
        // 1. 今天下雨,决定开车
        std::cout << "\n>>> 用户选择:驾车模式";
        amap.setStrategy(std::make_unique<DriveStrategy>());
        amap.executeRoutePlanning(startPoint, endPoint);
    
        // 2. 突然发现车牌今天限行,改坐地铁吧
        std::cout << "\n>>> 用户切换:公交模式";
        amap.setStrategy(std::make_unique<PublicTransitStrategy>());
        amap.executeRoutePlanning(startPoint, endPoint);
    
        // 3. 下班了,天气不错,离家近,走回去
        std::cout << "\n>>> 用户切换:步行模式";
        amap.setStrategy(std::make_unique<WalkStrategy>());
        amap.executeRoutePlanning(startPoint, endPoint);
    
        return 0;
    }
    • 补充:也可以通过回调函数的方式实现轻量的
  • 优点

    • 热插拔 :在程序运行过程中,导航软件(Navigator)可以随意更换内部的算法引擎(通过 setStrategy),而且不需要重启软件,也不需要重新实例化导航软件本身。
    • 彻底解耦 :如果明天你想加一个"骑共享单车"的策略,你只需要新建一个 BikeStrategy 类继承 RouteStrategy 就行了。Navigator 类的代码哪怕一个标点符号都不用改! 这就是完美的面向对象设计。

其他

  • 代理模式
    • 用途:为其他对象提供一种代理以控制对这个对象的访问。
    • C++实现特点 :常用于延迟加载、权限控制或日志记录。实际上,现代C++中的智能指针(如 std::shared_ptrstd::unique_ptr)就是一种代理模式的应用,它们代理了裸指针,提供了自动的生命周期管理。
  • 装饰器模式 (Decorator)
    • 用途:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
    • C++实现特点:通过组合来实现,装饰器类包含一个指向基类接口的指针(或智能指针),可以在调用原生方法前后注入新的行为。
相关推荐
keep intensify2 小时前
深度解析TCP三次握手四次挥手
网络·c++·后端·网络协议·tcp/ip·golang
星轨初途2 小时前
郑州轻工业大学“筑梯杯” 2025级新生程序设计大赛暨省内高校邀请赛——题解
android·c++·经验分享·笔记·算法
淮南颂恩少儿编程2 小时前
淮南少儿编程 | CSP-J真题详解:在淮南也有接地气的算法课
c++·人工智能·python·深度学习·算法·青少年编程·蓝桥杯
m0_748873552 小时前
模板编译期排序算法
开发语言·c++·算法
2401_842623652 小时前
基于C++的爬虫框架
开发语言·c++·算法
无限进步_2 小时前
【C++】获取字符串最后一个单词长度的多种解法
开发语言·c++·ide·windows·git·github·visual studio
沈阳信息学奥赛培训2 小时前
#define 和 typedef 的区别
开发语言·c++
程序猿编码2 小时前
轻量又灵活:一款伪造TCP数据包的iptables扩展实现解析(C/C++代码实现)
linux·c语言·网络·c++·tcp/ip·内核·内核模块
j_xxx404_2 小时前
LeetCode模拟算法精解I:替换问号,提莫攻击与Z字形变换
开发语言·数据结构·c++·算法·leetcode