当前位置: 首页 > article >正文

实战设计模式之组合模式

概述

        组合模式是一种结构型设计模式,允许我们将对象组合成树形结构,以表示部分和整体的层次关系。通过这种方式,我们可以统一地处理单个对象和对象组合。换句话说,组合模式使得客户代码能够忽略对象的层次结构,以一致的方式对待个体和集合。

        文件系统是运用组合模式的一个典型例子:计算机上的文件夹(或目录)可以包含其他文件夹或文件,而文件夹本身又可以被包含在更大的文件夹中。这种层次化的结构,允许用户以一致的方式来处理单个文件和整个文件夹。

基本原理

        组合模式的主要目标是:让客户端能够透明地处理单个对象和对象组合。为了实现这一点,组合模式通常定义了一个接口或抽象类,所有叶子节点(即不可再分的对象)和组合节点(即包含其他对象的对象)都实现了这个接口或继承了这个抽象类。这样一来,无论是叶子节点还是组合节点,它们对外呈现的接口都是相同的,从而实现了“同质化”。组合模式主要由以下四个核心组件构成。

        1、组件接口。这是组合模式中最顶层的接口,定义了所有组件共有的操作和属性。无论是叶子节点还是组合节点,都必须实现这些方法,这使得客户端代码可以统一地处理单个对象和对象组合。

        2、叶子节点。叶子节点是不可再分的基本单元,它实现了组件接口中定义的方法,但不会包含任何子组件。叶子节点代表的是最底层的对象,比如:文件系统中的具体文件、组织架构中的员工等。

        3、组合节点。组合节点是包含其他组件(可以是叶子节点或其他组合节点)的对象。它同样实现了组件接口,并且负责管理其内部的子组件集合。通过这种方式,组合节点可以递归地构建更复杂的结构。

        4、客户端。客户端通过组件接口与所有类型的组件交互,而无需关心具体是叶子节点还是组合节点。这种透明性,简化了客户端的设计和实现。

        基于上面的核心组件,组合模式的实现主要有以下四个步骤。

        1、定义组件接口。创建一个接口,声明所有组件都应该实现的方法。这包括基本操作,以及可能的组合相关操作(如果适用的话)。对于那些只适用于组合节点的方法,可以在接口中定义默认实现,以便叶子节点可以选择性地忽略它们。

        2、实现叶子节点。为每一个不可再分的基本单位创建具体的叶子类,并实现组件接口中定义的方法。由于叶子节点不包含子组件,所以与组合有关的操作可以直接抛出异常或留空。

        3、实现组合节点。创建一个组合类,该类不仅实现了组件接口,还提供了管理子组件的方法。组合节点需要维护一个子组件列表,并在适当的时候调用子组件的相关方法。

        4、编写客户端代码。客户端代码应当尽可能地依赖组件接口,而不是具体的实现类。这样做的好处是可以轻松地替换或扩展不同类型的组件,而无需修改客户端逻辑。

实战解析

        在下面的实战代码中,我们使用组合模式模拟了文件系统的树状层次结构。

        首先,我们定义了组件接口类CComponent。它是所有组件的基类,声明了四个纯虚函数:Operation、Add、Remove、GetChild,以及一个非纯虚函数GetName。默认情况下,Add、Remove和GetChild抛出异常,因为并非所有组件都需要实现这些方法。

        接下来,我们实现了叶子节点CFile。CFile类继承自CComponent,代表文件系统中的单个文件。它实现了 Operation方法以输出文件名,并实现了GetName方法以返回文件名。

        同时,我们还实现了组合节点CFolder。CFolder类同样继承自CComponent,但与CFile不同的是,它可以包含其他CComponent对象(即文件或其他文件夹)。CFolder类实现了Operation方法,该方法会递归地调用其所有子组件的Operation方法。此外,它还实现了Add、Remove和GetChild方法,用于管理子组件集合。CFolder的析构函数负责清理所有子组件,防止内存泄漏。

        最后,在main函数中,我们创建了几个文件和文件夹实例。先将两个文件添加到一个文件夹pFolder1中,再将这个文件夹与其他文件一起添加到另一个更大的文件夹pFolder2中。通过调用最外层文件夹pFolder2的Operation方法,我们递归地显示了整个文件系统的结构。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

// 定义组件接口
class CComponent
{
public:
    virtual ~CComponent() {}

    virtual void Operation() const = 0;

    virtual void Add(CComponent* pChild)
    {
        throw runtime_error("Not supported");
    }

    virtual void Remove(const string& name)
    {
        throw runtime_error("Not supported");
    }

    virtual CComponent* GetChild(int index)
    {
        throw runtime_error("Not supported");
    }

    virtual string GetName() const = 0;
};

// 实现叶子节点:文件
class CFile : public CComponent
{
public:
    CFile(const string& strName) : m_strName(strName) {}

    void Operation() const override
    {
        cout << "File: " << m_strName << endl;
    }

    string GetName() const override
    {
        return m_strName;
    }

private:
    string m_strName;
};

// 实现组合节点:文件夹
class CFolder : public CComponent
{
public:
    CFolder(const string& strName) : m_strName(strName) {}

    ~CFolder()
    {
        // 清理所有子组件
        for (auto pChild : m_vctChildren)
        {
            delete pChild;
        }
    }

    void Operation() const override
    {
        cout << "Folder: " << m_strName << endl;
        for (auto pChild : m_vctChildren)
        {
            pChild->Operation();
        }
    }

    void Add(CComponent* pChild) override
    {
        m_vctChildren.push_back(pChild);
    }

    void Remove(const string& name) override
    {
        m_vctChildren.erase(remove_if(m_vctChildren.begin(), m_vctChildren.end(),
            [&name](CComponent* pChild) {
                return pChild->GetName() == name;
            }),
            m_vctChildren.end());
    }

    CComponent* GetChild(int index) override
    {
        if (index >= 0 && index < (int)m_vctChildren.size())
        {
            return m_vctChildren[index];
        }

        throw out_of_range("Index out of range");
    }

    string GetName() const override
    {
        return m_strName;
    }

private:
    string m_strName;
    vector<CComponent*> m_vctChildren;
};

int main()
{
    // 创建文件和文件夹实例
    CComponent* pFile1 = new CFile("file1.txt");
    CComponent* pFile2 = new CFile("file2.txt");
    CComponent* pFolder1 = new CFolder("folder1");
    CComponent* pFolder2 = new CFolder("folder2");

    // 将文件添加到文件夹中
    pFolder1->Add(pFile1);
    pFolder1->Add(pFile2);
    pFolder2->Add(new CFile("file3.txt"));
    pFolder2->Add(pFolder1);

    // 操作文件夹,自动递归操作其中的文件
    pFolder2->Operation();

    // 自动清理其包含的所有子组件
    delete pFolder2;
    return 0;
}

总结

        组合模式支持开闭原则,即对扩展开放,对修改关闭。新的组件类型可以很容易地添加到系统中,而不会影响现有的代码。组合模式允许构建复杂的递归结构,比如:文件系统的目录和文件、组织架构中的部门和个人等,这样的结构能够很好地模拟现实世界中的分层关系。由于所有组件都实现了相同的接口,因此可以在不同的上下文中复用这些组件,并且可以根据需要动态地增加或移除子组件。

        但如果组合节点承担了过多的责任(比如:管理子组件、执行业务逻辑等),可能会使类变得过于庞大和复杂,进而违背单一职责原则。为了防止这种情况发生,应该尽量保持每个类的职责单一明确。另外,对于大型树状结构,遍历所有节点可能会导致性能问题,尤其是在频繁调用的情况下。此时,可能需要优化访问模式,或者引入缓存机制。


http://www.kler.cn/a/551295.html

相关文章:

  • 基于python深度学习遥感影像地物分类与目标识别、分割实践技术应用
  • HBase Shell
  • 【BUG】Ubuntu|有nvcc,没有nvidia-smi指令,找不到nvidia-driver安装包
  • 机器学习之熵的计算方法及香农信息熵的含义
  • 使用右侧值现象来处理一个word导入登记表的需求
  • 像素绽放PixelBloom(AiPPT.com) 联合创始人蒲世林:创新者的窘境就是新物种的机会
  • 代码随想录day12
  • 【第1章:深度学习概览——1.6 深度学习框架简介与选择建议】
  • DeepSeek-R1本地部署详细指南!(Ollama+Chatbox AI+Open WebUI)
  • 开源赋能,智造未来:Odoo+工业物联网,解锁智能工厂新范式——以真实案例解读制造业数字化转型的降本增效密码
  • DeepSeek教unity------MessagePack-04
  • 零基础学QT、C++(一)安装QT
  • 深入解析 Vue 3 编译宏:揭开 `<script setup>` 的魔法面纱
  • 力扣-二叉树-513 找二叉树左下角的值
  • python学习笔记,python处理 Excel、Word、PPT 以及邮件自动化办公
  • Qt creater 出现“启动程序失败,路径或者权限错误”解决方法
  • gozero实现数据库MySQL单例模式连接
  • Linux探秘坊-------8.进程详解
  • PyTorch入门实战:从零搭建你的第一个神经网络
  • (尚硅谷 Java 学习 B 站大学版)Day17 多态练习