设计模式

设计模式

一、设计模式概述

1. 设计模式是什么

设计模式是指在软件开发中,经过验证的、用于解决在特定环境下重复出现的特定问题的解决方案。它是一套被反复使用、多数人知晓、经过分类编目的代码设计经验的总结。

本质:设计模式是解决软件开发过程中问题的固定套路,类似于哲学中的方法论或武功秘籍,需要一定的工程代码量积累才能深刻理解和灵活运用。

使用原则:

  • 慎用原则:必须在明确需求变化方向且变化点经常出现时才使用设计模式,否则过度设计会增加系统复杂度。
  • 避免过度封装:不要陷入过度封装或滥用设计模式的误区,保持代码简洁。

2. 设计模式的来源

设计模式并不是凭空创造的,而是通过满足设计原则后慢慢迭代演化出来的。当某个问题在不同场景中重复出现,并且有成熟的解决方案时,这些方案逐渐被总结成设计模式。

学习路径:

  • 先决条件:首先要掌握面向对象设计原则。
  • 实践积累:通过工程实践逐步掌握,在已有项目中识别和运用设计模式。

3. 设计模式解决了什么问题

设计模式的核心价值在于应对需求变化。软件需求往往既有稳定部分又有变化部分,设计模式的目标是通过最小修改来适应需求变化,避免代码大规模重构。

适用前提:稳定与变化共存。如果所有需求都稳定,无需设计模式;如果所有需求都变化无常,更适合用脚本语言快速开发(如游戏逻辑)。

经典比喻(整洁房间与好动的猫):

  • 稳定点:整洁的房间(希望保持的系统状态)
  • 变化点:好动的猫(可能引起变化的需求)
  • 解决方案:将猫关在笼子里(让变化在有限范围内发生,即封装变化点)

4. 设计模式学习基础

4.1 面向对象三大特性

封装:隐藏实现细节,实现模块化。通过访问权限控制(public/protected/private)限定外部访问。

继承:无需修改原有类,通过继承扩展功能。C++支持多继承,而其他语言如Java、C#只支持单继承。

多态:设计模式最重要的特性,许多模式依赖多态实现。分为两类:

  • 静态多态:函数重载(相同函数名,不同参数)
  • 动态多态:继承中的虚函数重写,通过基类指针或引用调用派生类重写的函数

虚函数机制:编译器为每个包含虚函数的类生成虚函数表(vtable),表中存储虚函数指针。对象内存布局最前面有一个虚函数表指针(vptr),指向对应的虚函数表。当通过基类指针调用虚函数时,运行时根据对象的实际类型通过vptr找到正确的函数地址,实现动态绑定(晚绑定)。

4.2 设计原则

学习设计模式前必须掌握以下设计原则,它们是模式的指导思想和评价标准。

依赖倒置:高层模块不应依赖低层模块,两者都应依赖抽象。抽象不应依赖细节,细节应依赖抽象。例如,自动驾驶系统(高层)和车企(低层)都应依赖行业标准(抽象层),实现解耦。

开闭原则:对扩展开放,对修改关闭。当需求变化时,应通过扩展(如新增子类)而非修改现有代码来适应变化。

面向接口编程:强调依赖抽象接口而非具体实现。实现方和使用方都依赖同一接口,降低耦合。

封装变化点:将可能变化的部分封装起来,减少修改影响范围。如将好动的猫关在笼子里,保持房间稳定。

单一职责:一个类应该只有一个引起变化的原因。职责即变化方向,职责越多,变化点越多。

里氏替换:子类必须能够替换父类而不影响程序正确性。子类不应改变父类原有的功能契约,通常通过虚函数重写实现。

接口隔离:客户端不应被迫依赖其不使用的接口。应将胖接口细化为多个小接口,减少不必要的依赖。

组合优于继承:优先使用对象组合而非类继承来实现代码复用。组合具有更好的灵活性和更低的耦合度。

最小知道原则(迪米特法则):一个对象应对其他对象保持最少的了解。只与直接朋友通信(成员变量、方法参数、返回值),减少类间耦合。

这些原则相互补充,共同指导面向对象设计。

二、模板方法模式

1. 定义与特点

定义:定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以不改变算法结构即可重定义算法的某些特定步骤。(GoF《设计模式》)

关键特征:

  • 稳定点:算法骨架(父类定义)
  • 变化点:子流程的具体实现(子类实现)
  • 通过继承机制实现扩展,符合“开闭原则”。

2. 稳定点和变化点分析

稳定点 变化点
算法骨架:固定不变的流程结构 子流程实现:可替换的具体步骤实现
控制反转:父类控制子流程调用顺序 扩展方式:通过子类重写父类方法
保护机制:通过protected限定子流程访问权限 -

3. 代码结构

// 抽象父类
class AbstractClass {
public:
    // 模板方法,定义算法骨架
    void TemplateMethod() final {
        Step1();
        Step2();
        Hook(); // 可选步骤
    }

protected:
    virtual void Step1() = 0; // 纯虚函数,必须由子类实现
    virtual void Step2() = 0;
    virtual void Hook() {}    // 钩子方法,可选覆盖
};

// 具体子类
class ConcreteClassA : public AbstractClass {
protected:
    void Step1() override { /* 具体实现A */ }
    void Step2() override { /* 具体实现A */ }
};

class ConcreteClassB : public AbstractClass {
protected:
    void Step1() override { /* 具体实现B */ }
    void Step2() override { /* 具体实现B */ }
    void Hook() override { /* 可选扩展 */ }
};

实现要点:

  • 模板方法通常声明为final防止子类重写骨架。
  • 可变步骤用protected虚函数,控制子类访问权限。
  • 钩子方法(hook)提供默认空实现,子类可选择覆盖。

4. 符合的设计原则

  • 开闭原则:新增行为只需创建子类,无需修改父类。
  • 里氏替换:子类可替换父类出现的位置。
  • 单一职责:父类负责流程控制,子类负责具体实现。
  • 好莱坞原则:“不要调用我们,我们会调用你”,父类控制子类方法调用时机。

5. 应用案例:动物园表演流程

假设动物园有固定表演流程:开场→节目A→节目B→终场。但节假日需要替换节目内容。

class ZooShow {
public:
    void Show() { // 模板方法
        Open();       // 开场
        PlayA();      // 节目A
        PlayB();      // 节目B
        Close();      // 终场
    }

protected:
    virtual void PlayA() { /* 默认节目A */ }
    virtual void PlayB() { /* 默认节目B */ }

private:
    void Open() { /* 固定开场 */ }
    void Close() { /* 固定终场 */ }
};

// 国庆特别节目
class NationalDayShow : public ZooShow {
protected:
    void PlayA() override { /* 国庆特别节目A */ }
    void PlayB() override { /* 国庆特别节目B */ }
};

// 使用
ZooShow* show = new NationalDayShow();
show->Show(); // 执行国庆特别版流程

三、观察者模式

1. 定义

定义:定义对象间的一种一对多(变化)的依赖关系,当一个对象(Subject)状态改变时,所有依赖它的对象都得到通知并自动更新。(GoF《设计模式》)

关键特征:

  • 一对多关系:一个被观察者(主题)对应多个观察者。
  • 自动通知:主题状态变化时,自动通知所有注册的观察者。

2. 稳定点和变化点

稳定点 变化点
一对多的依赖关系结构 观察者数量:可动态增减
通知机制:变化时自动响应 观察者类型:可自由扩展
注册/注销接口 观察者更新行为

3. 代码实现

#include <list>
#include <algorithm>

// 观察者接口
class IObserver {
public:
    virtual ~IObserver() {}
    virtual void Update(float temperature) = 0;
};

// 主题接口(被观察者)
class ISubject {
public:
    virtual ~ISubject() {}
    virtual void Attach(IObserver* observer) = 0;
    virtual void Detach(IObserver* observer) = 0;
    virtual void Notify() = 0;
};

// 具体主题:气象数据中心
class WeatherData : public ISubject {
public:
    void Attach(IObserver* observer) override {
        observers_.push_back(observer);
    }

    void Detach(IObserver* observer) override {
        observers_.remove(observer);
    }

    void Notify() override {
        for (auto* observer : observers_) {
            observer->Update(temperature_);
        }
    }

    void SetTemperature(float temp) {
        temperature_ = temp;
        Notify(); // 温度变化时通知所有观察者
    }

private:
    std::list<IObserver*> observers_;
    float temperature_ = 0.0f;
};

// 具体观察者:显示终端
class DisplayA : public IObserver {
public:
    void Update(float temperature) override {
        std::cout << "DisplayA: 当前温度 " << temperature << std::endl;
    }
};

class DisplayB : public IObserver {
public:
    void Update(float temperature) override {
        std::cout << "DisplayB: 温度 " << temperature << " (华氏度: " << temperature * 9/5 + 32 << ")" << std::endl;
    }
};

4. 符合的设计原则

  • 面向接口编程:主题和观察者均依赖抽象接口。
  • 接口隔离:IObserver只包含Update方法,IDisplay(如有)只包含Show方法。
  • 封装变化点:观察者数量变化通过Attach/Detach封装。
  • 最小知道原则:主题只需知道观察者接口,不关心具体实现。
  • 开闭原则:新增观察者无需修改主题代码。

5. 扩展方式

增加新观察者:只需继承IObserver实现新类,然后调用Attach注册。例如增加一个日志记录观察者:

class Logger : public IObserver {
    void Update(float temperature) override {
        std::cout << "Log: 温度记录 " << temperature << std::endl;
    }
};

// 使用
WeatherData wd;
DisplayA da;
DisplayB db;
Logger lg;
wd.Attach(&da);
wd.Attach(&db);
wd.Attach(&lg);
wd.SetTemperature(25.5);

四、策略模式

1. 定义

定义:定义一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法独立于使用它的客户而变化。(GoF《设计模式》)

关键特征:

  • 算法之间是平行可替换的关系。
  • 客户程序与算法保持一对一调用关系,通过组合方式使用策略。

2. 稳定点和变化点

稳定点 变化点
调用关系:客户程序调用算法的固定方式 算法新增:通过继承接口新增具体策略类
抽象方法:策略接口定义 算法修改:在现有策略类中修改实现
依赖注入:通过构造函数或方法传递策略 -

3. 代码实现:促销策略

// 策略接口
class IPromotionStrategy {
public:
    virtual ~IPromotionStrategy() {}
    virtual double Calculate(double price) = 0;
};

// 具体策略:国庆促销(满100减20)
class NationalDayPromotion : public IPromotionStrategy {
public:
    double Calculate(double price) override {
        return price >= 100 ? price - 20 : price;
    }
};

// 具体策略:圣诞促销(打8折)
class ChristmasPromotion : public IPromotionStrategy {
public:
    double Calculate(double price) override {
        return price * 0.8;
    }
};

// 环境类:购物车
class ShoppingCart {
public:
    void SetStrategy(IPromotionStrategy* strategy) {
        strategy_ = strategy;
    }

    double Checkout(double totalPrice) {
        if (strategy_) {
            return strategy_->Calculate(totalPrice);
        }
        return totalPrice;
    }

private:
    IPromotionStrategy* strategy_ = nullptr;
};

// 使用
ShoppingCart cart;
ChristmasPromotion christmas;
cart.SetStrategy(&christmas);
double finalPrice = cart.Checkout(200); // 160

4. 符合的设计原则

  • 开闭原则:新增促销策略无需修改购物车代码。
  • 依赖倒置:购物车依赖抽象策略接口。
  • 组合优于继承:通过组合策略对象实现功能扩展。
  • 接口隔离:策略接口只包含必要方法。

5. 应用场景

  • 多种促销活动切换
  • 支付方式选择(支付宝、微信、信用卡)
  • 排序算法动态选择

五、单例模式

1. 定义

定义:保证一个类仅有一个实例,并提供一个全局访问点。(GoF《设计模式》)

关键特征:

  • 构造私有化
  • 通过静态方法获取实例
  • 考虑线程安全

2. 稳定点和变化点

稳定点 变化点
实例唯一性 多单例类管理(需抽象通用实现)
全局访问接口 线程安全实现方式
禁止拷贝/赋值 生命周期管理

3. 单例模式的演进(C++版本)

版本一:基础懒汉式(非线程安全)

class Singleton {
public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }

private:
    Singleton() {}
    ~Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance_;
};

Singleton* Singleton::instance_ = nullptr;

问题:多线程下可能创建多个实例,且未释放资源。

版本二:加atexit释放资源

class Singleton {
public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            instance_ = new Singleton();
            atexit(Destructor);
        }
        return instance_;
    }

private:
    static void Destructor() {
        if (instance_ != nullptr) {
            delete instance_;
            instance_ = nullptr;
        }
    }
    // ... 其他成员同上
};

解决了资源释放,但仍有线程安全问题。

版本三:加锁(双重检查锁定)

#include <mutex>

class Singleton {
public:
    static Singleton* GetInstance() {
        if (instance_ == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance_ == nullptr) {
                instance_ = new Singleton();
                atexit(Destructor);
            }
        }
        return instance_;
    }

private:
    static std::mutex mutex_;
    static Singleton* instance_;
};

std::mutex Singleton::mutex_;
Singleton* Singleton::instance_ = nullptr;

存在问题:new操作可能被CPU重排序,导致其他线程获取到未完全初始化的对象。

版本四:使用原子操作和内存屏障

#include <atomic>
#include <mutex>

class Singleton {
public:
    static Singleton* GetInstance() {
        Singleton* tmp = instance_.load(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);
            tmp = instance_.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                instance_.store(tmp, std::memory_order_release);
                atexit(Destructor);
            }
        }
        return tmp;
    }

private:
    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_(nullptr);
std::mutex Singleton::mutex_;

此版本通过内存序确保正确性,但代码复杂。

版本五:C++11 Magic Static(最佳实践)

C++11保证局部静态变量的初始化是线程安全的,且只会初始化一次。

class Singleton {
public:
    static Singleton& GetInstance() {
        static Singleton instance;
        return instance;
    }

private:
    Singleton() {}
    ~Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点:简洁、线程安全、自动回收。推荐使用。

版本六:模板化单例(解决多类复用)

template<typename T>
class Singleton {
public:
    static T& GetInstance() {
        static T instance;
        return instance;
    }

protected:
    Singleton() = default;
    virtual ~Singleton() = default;

private:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

// 使用
class Logger : public Singleton<Logger> {
    // 需要让基类能访问派生类构造函数
    friend class Singleton<Logger>;
private:
    Logger() {}
};

// 访问
Logger& logger = Logger::GetInstance();

通过模板实现单例代码复用,注意派生类需将基类声明为友元。

4. 应用场景

  • 配置管理器
  • 日志系统
  • 线程池
  • 数据库连接池

六、工厂方法模式

1. 定义

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。(GoF《设计模式》)

关键特征:

  • 将对象的创建与使用分离
  • 通过工厂接口创建产品对象

2. 稳定点和变化点

稳定点 变化点
工厂接口和产品接口 具体产品类型
创建对象的调用方式 产品的创建过程(可能复杂)

3. 例题:导出数据接口

假设需要支持多种格式导出(XML、JSON、CSV等),每种格式的初始化可能涉及加载库、配置参数等复杂过程。

// 产品接口
class IExport {
public:
    virtual ~IExport() {}
    virtual bool Export(const std::string& data) = 0;
};

// 工厂接口
class IExportFactory {
public:
    virtual ~IExportFactory() {}
    virtual IExport* CreateExport() = 0; // 工厂方法
};

// 具体产品:JSON导出器
class JsonExporter : public IExport {
public:
    bool Export(const std::string& data) override {
        // 实现JSON导出
        std::cout << "Exporting as JSON: " << data << std::endl;
        return true;
    }
};

// 具体工厂:JSON工厂
class JsonFactory : public IExportFactory {
public:
    IExport* CreateExport() override {
        // 可能包含复杂初始化:加载库、配置等
        return new JsonExporter();
    }
};

// 使用
void Client(IExportFactory* factory) {
    IExport* exporter = factory->CreateExport();
    exporter->Export("some data");
    delete exporter;
}

int main() {
    JsonFactory jsonFactory;
    Client(&jsonFactory);
}

4. 符合的设计原则

  • 开闭原则:新增产品只需添加新工厂和新产品类,无需修改现有代码。
  • 依赖倒置:客户依赖抽象工厂和产品接口。
  • 单一职责:工厂负责创建,产品负责功能。

5. 与模板方法的联系

工厂方法可以看作是模板方法的特例:父类定义创建产品的骨架(如CreateExport),子类实现具体创建逻辑。在模板方法模式中,算法骨架可能包含多个步骤,而工厂方法通常只有一个创建步骤。


七、抽象工厂模式

1. 定义

定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(GoF《设计模式》)

关键特征:

  • 工厂负责创建一组产品(产品族)
  • 产品之间通常有内在联系

2. 例题:导出与导入

在导出功能基础上增加导入功能,要求同一格式的导出器和导入器一起创建(如XML导出和XML导入)。

// 产品接口
class IExport { /* 同前 */ };
class IImport {
public:
    virtual ~IImport() {}
    virtual bool Import(std::string& data) = 0;
};

// 抽象工厂
class IDataProcessorFactory {
public:
    virtual ~IDataProcessorFactory() {}
    virtual IExport* CreateExporter() = 0;
    virtual IImport* CreateImporter() = 0;
};

// 具体产品:XML导出/导入
class XmlExporter : public IExport {
public:
    bool Export(const std::string& data) override {
        std::cout << "XML导出: " << data << std::endl;
        return true;
    }
};

class XmlImporter : public IImport {
public:
    bool Import(std::string& data) override {
        std::cout << "XML导入" << std::endl;
        data = "parsed XML data";
        return true;
    }
};

// 具体工厂:XML工厂
class XmlFactory : public IDataProcessorFactory {
public:
    IExport* CreateExporter() override { return new XmlExporter(); }
    IImport* CreateImporter() override { return new XmlImporter(); }
};

// 使用
void Client(IDataProcessorFactory* factory) {
    IExport* exporter = factory->CreateExporter();
    IImport* importer = factory->CreateImporter();
    exporter->Export("data");
    std::string importedData;
    importer->Import(importedData);
    delete exporter;
    delete importer;
}

3. 符合的设计原则

  • 开闭原则:新增一个格式族(如JSON),只需添加相应的产品和工厂,不影响现有代码。
  • 依赖倒置:客户依赖抽象工厂和抽象产品。
  • 接口隔离:工厂接口包含多个创建方法,但每个方法对应一个产品类型。

4. 适用场景

  • 需要创建一组相关对象(如UI组件:不同风格下按钮、文本框一起出现)
  • 系统需要与多个产品库交互

八、责任链模式

1. 定义

定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。(GoF《设计模式》)

关键特征:

  • 处理对象构成链状结构
  • 请求沿链传递,可被任一节点处理并终止
  • 处理顺序和节点数量可动态调整

2. 稳定点和变化点

稳定点 变化点
请求沿固定顺序传递 处理节点数量可扩展
可打断机制(处理即终止) 处理顺序可调整
链的构建方式 处理条件可修改

3. 例题:请假审批

场景:1天内由主程序审批,1-3天由项目经理审批,3天以上由老板审批。

#include <iostream>

// 处理者接口
class IHandler {
public:
    virtual ~IHandler() {}
    virtual void SetNext(IHandler* next) { next_ = next; }
    virtual void Handle(int days) {
        if (CanHandle(days)) {
            Process(days);
        } else if (next_) {
            next_->Handle(days);
        } else {
            std::cout << "无人能处理" << days << "天的请假" << std::endl;
        }
    }

protected:
    virtual bool CanHandle(int days) = 0;
    virtual void Process(int days) = 0;

private:
    IHandler* next_ = nullptr;
};

// 具体处理者:主程序(<=1天)
class MainProgram : public IHandler {
protected:
    bool CanHandle(int days) override { return days <= 1; }
    void Process(int days) override {
        std::cout << "主程序批准" << days << "天假期" << std::endl;
    }
};

// 项目经理(1-3天)
class ProjectManager : public IHandler {
protected:
    bool CanHandle(int days) override { return days > 1 && days <= 3; }
    void Process(int days) override {
        std::cout << "项目经理批准" << days << "天假期" << std::endl;
    }
};

// 老板(>3天)
class Boss : public IHandler {
protected:
    bool CanHandle(int days) override { return days > 3; }
    void Process(int days) override {
        std::cout << "老板批准" << days << "天假期" << std::endl;
    }
};

// 构建链
int main() {
    MainProgram p1;
    ProjectManager p2;
    Boss p3;
    p1.SetNext(&p2);
    p2.SetNext(&p3);

    p1.Handle(2); // 项目经理处理
    p1.Handle(5); // 老板处理
    p1.Handle(0); // 主程序处理
}

4. 符合的设计原则

  • 开闭原则:新增处理者只需继承IHandler,调整链即可。
  • 单一职责:每个处理者只负责自己范围内的判断和处理。
  • 依赖倒置:处理者之间依赖抽象接口。

5. 应用场景

  • 审批流程
  • 日志处理(不同级别输出到不同目标)
  • 过滤器/拦截器链

九、装饰器模式

1. 定义

定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。(GoF《设计模式》)

关键特征:

  • 通过组合而非继承扩展功能
  • 可以动态添加/撤销功能
  • 装饰器与被装饰对象实现同一接口,对客户端透明

2. 稳定点和变化点

稳定点 变化点
组件接口 装饰器的具体功能
装饰器的组合方式 可动态增删装饰器

3. 代码实现:咖啡加配料

#include <iostream>

// 抽象组件
class Coffee {
public:
    virtual ~Coffee() {}
    virtual double Cost() = 0;
    virtual std::string Description() = 0;
};

// 具体组件:简单咖啡
class SimpleCoffee : public Coffee {
public:
    double Cost() override { return 10.0; }
    std::string Description() override { return "简单咖啡"; }
};

// 抽象装饰器
class CoffeeDecorator : public Coffee {
public:
    CoffeeDecorator(Coffee* coffee) : coffee_(coffee) {}
    virtual ~CoffeeDecorator() { delete coffee_; }

protected:
    Coffee* coffee_;
};

// 具体装饰器:加牛奶
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    double Cost() override { return coffee_->Cost() + 5.0; }
    std::string Description() override {
        return coffee_->Description() + " + 牛奶";
    }
};

// 具体装饰器:加糖
class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    double Cost() override { return coffee_->Cost() + 2.0; }
    std::string Description() override {
        return coffee_->Description() + " + 糖";
    }
};

// 使用
int main() {
    Coffee* coffee = new SimpleCoffee();
    coffee = new MilkDecorator(coffee);
    coffee = new SugarDecorator(coffee);
    std::cout << coffee->Description() << " 价格: " << coffee->Cost() << std::endl;
    delete coffee;
}

4. 符合的设计原则

  • 开闭原则:新增装饰器不影响现有代码。
  • 组合优于继承:通过组合动态添加功能。
  • 依赖倒置:装饰器依赖抽象组件接口。

5. 应用场景

  • I/O流(Java的BufferedInputStream包装FileInputStream)
  • 图形界面组件(窗口加滚动条、边框)
  • 日志增强(添加时间戳、加密)

十、组合模式

1. 定义

定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。(GoF《设计模式》)

关键特征:

  • 树形结构:叶子节点和组合节点实现同一接口
  • 透明性:客户端可以一致地处理所有节点

2. 例题:文件系统

#include <iostream>
#include <vector>
#include <memory>

// 组件接口
class FileSystemNode {
public:
    virtual ~FileSystemNode() {}
    virtual void Display(int depth = 0) = 0;
    virtual void Add(FileSystemNode* node) {} // 默认空实现,叶子节点不适用
    virtual void Remove(FileSystemNode* node) {} // 同上
};

// 叶子节点:文件
class File : public FileSystemNode {
public:
    File(const std::string& name) : name_(name) {}
    void Display(int depth = 0) override {
        std::cout << std::string(depth, '-') << name_ << std::endl;
    }

private:
    std::string name_;
};

// 组合节点:目录
class Directory : public FileSystemNode {
public:
    Directory(const std::string& name) : name_(name) {}
    void Add(FileSystemNode* node) override {
        children_.push_back(node);
    }
    void Remove(FileSystemNode* node) override {
        // 简单实现,实际需查找并删除
        auto it = std::find(children_.begin(), children_.end(), node);
        if (it != children_.end()) children_.erase(it);
    }
    void Display(int depth = 0) override {
        std::cout << std::string(depth, '+') << name_ << "/" << std::endl;
        for (auto* child : children_) {
            child->Display(depth + 2);
        }
    }
    ~Directory() {
        for (auto* child : children_) delete child;
    }

private:
    std::string name_;
    std::vector<FileSystemNode*> children_;
};

// 使用
int main() {
    Directory* root = new Directory("root");
    Directory* docs = new Directory("docs");
    File* a = new File("a.txt");
    File* b = new File("b.txt");
    docs->Add(a);
    docs->Add(b);
    root->Add(docs);
    root->Add(new File("readme.md"));

    root->Display();
    delete root;
}

3. 符合的设计原则

  • 开闭原则:新增节点类型(如链接文件)只需继承FileSystemNode。
  • 透明性:客户端用一致的方式操作文件和目录。
  • 接口隔离:部分方法(Add/Remove)可能只适用于组合节点,但为了一致性保留,可抛出异常或空实现。

4. 应用场景

  • 文件系统
  • 组织结构树
  • 图形界面中的容器与控件
posted @ 2026-03-04 19:23  xggx  阅读(31)  评论(0)    收藏  举报