• C 语言设计模式(结构型)


    代理模式

    C语言中,代理模式通常用于实现对象的间接访问。代理模式是一种结构型设计模式,它允许你提供一个代理对象来控制对另一个对象的访问。这种类型的设计模式属于结构型模式,因为它创建了对象的代理,以控制对它们的访问。

    1. 抽象主题(Subject):定义了真实主题和代理主题的共同接口,这样代理就可以用来代替真实主题。

    2。 真实主题(Real Subject):定义了实际对象,代理对象是对它的一个引用。代理模式中的大部分工作都由真实主题完成,而代理主要负责将请求传递给真实主题。

    1. 代理主题(Proxy):保存一个引用使得代理可以访问真实主题,并提供与真实主题相同的接口,这样客户端就无需知道代理和真实主题的区别。

    代理模式通常被用来解决以下一些问题:

    • 远程代理(Remote Proxy):在不同的地址空间中代表一个对象。这种情况下,代理对象负责将请求和参数进行编码,并向远程对象发送请求。

    • 虚拟代理(Virtual Proxy):用于按需创建昂贵对象,这样只有在真正需要时才会实例化真实对象。例如,一个图像浏览器可能只在需要显示图像时才会加载真正的图像数据。

    • 保护代理(Protection Proxy):用于控制对对象的访问,可以根据访问权限拒绝调用者的请求。

    场景

    1. 文件访问控制:在一个需要对文件进行访问控制的系统中,可以使用代理模式来创建一个文件访问代理,以控制对文件的读取和写入操作,实现对文件访问的安全管理。

    2. 网络通信:在网络通信中,可以使用代理模式来创建网络通信代理,以控制对网络资源的访问,实现网络通信的安全验证、加密解密等功能。

    3. 资源管理:在需要管理共享资源的系统中,可以使用代理模式来创建资源管理代理,以控制对资源的访问,实现资源的共享和保护。

    4. 缓存管理:在需要缓存数据以提高性能的系统中,可以使用代理模式来创建缓存代理,以控制对数据的访问,实现数据的缓存和更新策略。

    5. 权限管理:在需要对用户权限进行管理的系统中,可以使用代理模式来创建权限管理代理,以控制对资源的访问,实现对用户权限的控制和管理。

    示例

    #include 
    #include 
    #include 
    
    // 定义抽象主题接口
    struct File {
        void (*read)(struct File *);
        void (*write)(struct File *, const char *);
    };
    
    // 定义真实主题
    struct RealFile {
        struct File file; // 继承自抽象主题
        char *name;
    };
    
    // 真实主题的读操作实现
    void real_read(struct File *file) {
        struct RealFile *realFile = (struct RealFile *)file;
        printf("读取文件:%s\n", realFile->name);
    }
    
    // 真实主题的写操作实现
    void real_write(struct File *file, const char *data) {
        struct RealFile *realFile = (struct RealFile *)file;
        printf("写入文件:%s\n", realFile->name);
        printf("数据:%s\n", data);
    }
    
    // 定义代理主题
    struct ProxyFile {
        struct File file; // 继承自抽象主题
        struct RealFile *realFile;
    };
    
    // 代理主题的读操作实现
    void proxy_read(struct File *file) {
        struct ProxyFile *proxyFile = (struct ProxyFile *)file;
        printf("代理请求读取...\n");
        proxyFile->realFile->file.read(&(proxyFile->realFile->file));
    }
    
    // 代理主题的写操作实现
    void proxy_write(struct File *file, const char *data) {
        struct ProxyFile *proxyFile = (struct ProxyFile *)file;
        printf("代理请求写入...\n");
        proxyFile->realFile->file.write(&(proxyFile->realFile->file), data);
    }
    
    int main() {
        // 创建真实文件对象
        struct RealFile *realFile = malloc(sizeof(struct RealFile));
        realFile->name = strdup("example.txt");
        realFile->file.read = real_read;
        realFile->file.write = real_write;
    
        // 创建文件代理对象
        struct ProxyFile *proxyFile = malloc(sizeof(struct ProxyFile));
        proxyFile->realFile = realFile;
        proxyFile->file.read = proxy_read;
        proxyFile->file.write = proxy_write;
    
        // 使用代理对象读写文件
        proxyFile->file.read(&(proxyFile->file));
        proxyFile->file.write(&(proxyFile->file), "Hello, Proxy Pattern!");
    
        // 释放内存
        free(realFile->name);
        free(realFile);
        free(proxyFile);
    
        return 0;
    }
    
    • 输出结果
    代理请求读取...
    读取文件:example.txt
    代理请求写入...
    写入文件:example.txt
    数据:Hello, Proxy Pattern!
    

    门面模式

    C语言中,门面模式(Facade Pattern)是一种常见的设计模式,用于提供一个统一的接口,以简化一组复杂系统的使用。门面模式隐藏了系统的复杂性,为客户端提供了一个更简单的接口,从而使客户端不需要了解系统的内部实现细节。

    1. 门面(Facade):提供了一个简单的接口,用于将客户端的请求委派给系统内部的各个子系统。

    2. 子系统(Subsystems):实现了系统的各个功能,但是这些功能对客户端来说可能过于复杂。门面模式通过封装这些子系统,提供了一个更简单的接口供客户端使用。

    场景

    1. 复杂系统的简化接口:当一个系统由多个子系统组成,而客户端需要使用的功能较少或者不需要了解系统内部的复杂结构时,门面模式可以提供一个简化的接口,隐藏系统的复杂性,使得客户端更容易使用。

    2. 库或框架的封装:当开发一个库或框架时,为了提供更加友好的接口给用户使用,可以使用门面模式来封装库或框架的内部实现细节,使得用户只需调用简单的接口即可完成复杂的操作。

    3. 遗留系统的接口升级:当需要对一个遗留系统进行接口升级或者重构时,可以使用门面模式来封装原有的接口,提供一个向后兼容的接口给客户端使用,从而减少对客户端的影响。

    4. 跨平台开发:当需要在不同平台上开发应用程序时,可以使用门面模式来封装不同平台的差异,提供一个统一的接口给客户端使用,从而简化开发工作。

    5. 系统的分层设计:当一个系统被分为多个层次,每个层次都有不同的责任时,可以使用门面模式来定义每个层次的门面,使得每个层次都可以独立于其他层次进行开发和测试。

    示例

    #include 
    
    // 子系统1:CPU
    struct CPU {
        void (*run)(struct CPU *);
    };
    
    // 子系统1的操作实现
    void cpu_run(struct CPU *cpu) {
        printf("CPU开始运行\n");
    }
    
    // 子系统2:内存
    struct Memory {
        void (*load)(struct Memory *);
    };
    
    // 子系统2的操作实现
    void memory_load(struct Memory *memory) {
        printf("内存加载数据\n");
    }
    
    // 子系统3:硬盘
    struct HardDrive {
        void (*read)(struct HardDrive *);
    };
    
    // 子系统3的操作实现
    void harddrive_read(struct HardDrive *hardDrive) {
        printf("硬盘读取数据\n");
    }
    
    // 门面:计算机
    struct Computer {
        struct CPU cpu;
        struct Memory memory;
        struct HardDrive hardDrive;
        void (*start)(struct Computer *);
    };
    
    // 门面的操作实现
    void computer_start(struct Computer *computer) {
        printf("计算机启动中...\n");
        // 启动计算机时,依次启动CPU、内存和硬盘
        computer->cpu.run(&(computer->cpu));
        computer->memory.load(&(computer->memory));
        computer->hardDrive.read(&(computer->hardDrive));
        printf("计算机启动完成\n");
    }
    
    int main() {
        // 创建计算机对象
        struct Computer computer;
        // 初始化计算机对象的各个子系统
        computer.cpu.run = cpu_run;
        computer.memory.load = memory_load;
        computer.hardDrive.read = harddrive_read;
        // 初始化计算机对象的启动函数
        computer.start = computer_start;
    
        // 使用门面模式启动计算机
        computer.start(&computer);
    
        return 0;
    }
    
    • 输出结果
    计算机启动中...
    CPU开始运行
    内存加载数据
    硬盘读取数据
    计算机启动完成
    

    桥接模式

    C语言中,桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化。桥接模式通过将抽象部分与实现部分进行解耦,允许它们可以独立地进行变化,从而提高了系统的灵活性和可扩展性。

    1. 抽象部分(Abstraction):定义了抽象类接口,维护一个指向实现部分的引用。

    2. 扩展抽象部分(Refined Abstraction):继承自抽象部分,扩展了抽象类接口,增加了新的功能或行为。

    3. 实现部分(Implementor):定义了实现类接口,提供了实现类的基本操作。

    4. 具体实现部分(Concrete Implementor):实现了实现部分接口的具体类。

    场景

    1. 多维度变化的系统:当一个系统有多个维度的变化,而且这些维度都需要独立变化时,可以使用桥接模式将不同维度的变化进行解耦。例如,一个图形界面系统中,可能有多种不同的绘制方式和多种不同的控件类型,可以使用桥接模式将不同的绘制方式和控件类型进行解耦,使得系统更加灵活。

    2. 运行时切换实现:当一个系统需要在运行时动态地选择不同的实现时,可以使用桥接模式。例如,一个网络通信库可能需要支持多种不同的网络协议,可以使用桥接模式将不同的网络协议的实现进行解耦,使得系统可以在运行时动态地选择不同的网络协议。

    3. 多平台支持:当一个系统需要支持多个不同的平台时,可以使用桥接模式将不同平台的实现进行解耦。例如,一个图形库可能需要支持在Windows、Linux和macOS等多个平台上运行,可以使用桥接模式将不同平台的图形接口进行解耦,使得系统更易于扩展和维护。

    4. 数据库驱动程序:在数据库访问层中,不同的数据库可能有不同的实现方式,可以使用桥接模式将不同数据库的实现进行解耦,使得系统可以在运行时动态地选择不同的数据库实现。

    示例

    #include 
    
    // 实现部分接口
    struct Implementor {
        void (*operationImpl)(void); // 操作实现函数指针
    };
    
    // 具体实现部分A
    void operationImplA() {
        printf("具体实现部分A的操作\n");
    }
    
    // 具体实现部分B
    void operationImplB() {
        printf("具体实现部分B的操作\n");
    }
    
    // 抽象部分
    struct Abstraction {
        struct Implementor *impl; // 实现部分指针
        void (*operation)(struct Abstraction *); // 操作函数
    };
    
    // 扩展抽象部分
    void operation(struct Abstraction *abstraction) {
        abstraction->impl->operationImpl(); // 调用实现部分的操作
    }
    
    int main() {
        // 创建具体实现部分A的对象
        struct Implementor implA;
        implA.operationImpl = operationImplA;
    
        // 创建具体实现部分B的对象
        struct Implementor implB;
        implB.operationImpl = operationImplB;
    
        // 创建抽象部分对象,关联具体实现部分A
        struct Abstraction abstractionA;
        abstractionA.impl = &implA;
        abstractionA.operation = operation;
    
        // 调用抽象部分的操作,实际执行具体实现部分A的操作
        abstractionA.operation(&abstractionA);
    
        // 创建抽象部分对象,关联具体实现部分B
        struct Abstraction abstractionB;
        abstractionB.impl = &implB;
        abstractionB.operation = operation;
    
        // 调用抽象部分的操作,实际执行具体实现部分B的操作
        abstractionB.operation(&abstractionB);
    
        return 0;
    }
    
    • 输出结果
    具体实现部分A的操作
    具体实现部分B的操作
    

    适配器模式

    C语言中,适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类能够一起工作。适配器模式通过将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而不能一起工作的类可以一起工作。

    1. 目标接口(Target):客户端期望的接口,适配器模式通过适配器将被适配者的接口转换成目标接口。

    2. 被适配者(Adaptee):需要被适配的类,它的接口与目标接口不兼容。

    3. 适配器(Adapter):实现了目标接口,并包含一个被适配者的对象,它将客户端的请求转发给被适配者,并进行适配。

    场景

    1. 使用现有库或框架:当需要使用一个已经存在的库或框架,但它的接口与系统的其他部分不兼容时,可以使用适配器模式将它的接口转换成系统所期望的接口。

    2. 跨平台开发:在跨平台开发中,不同平台上的API可能存在差异,导致代码无法跨平台使用。可以使用适配器模式将不同平台上的API接口进行统一,使得代码可以跨平台使用。

    3. 日志系统的适配:当系统需要更换日志系统时,可以使用适配器模式将新的日志系统的接口适配成原有日志系统的接口,使得系统可以无缝切换日志系统而不需要修改大量代码。

    4. 数据库访问的适配:当系统需要支持多种不同的数据库时,可以使用适配器模式将不同数据库的访问接口适配成统一的接口,使得系统可以使用相同的代码访问不同的数据库。

    5. 设备驱动的适配:当系统需要支持多种不同的硬件设备时,可以使用适配器模式将不同设备的驱动接口适配成统一的接口,使得系统可以使用相同的代码操作不同的设备。

    示例

    #include 
    
    // 目标接口
    struct Target {
        void (*request)(struct Target *);
    };
    
    // 被适配者
    struct Adaptee {
        void (*specificRequest)(struct Adaptee *);
    };
    
    // 被适配者的特定请求实现
    void specificRequest(struct Adaptee *adaptee) {
        printf("被适配者的特定请求\n");
    }
    
    // 适配器
    struct Adapter {
        struct Target target; // 目标接口
        struct Adaptee adaptee; // 被适配者
    };
    
    // 适配器的请求实现
    void request(struct Target *target) {
        // 将目标接口的请求转发给被适配者的特定请求
        struct Adapter *adapter = (struct Adapter *)target;
        adapter->adaptee.specificRequest(&(adapter->adaptee));
    }
    
    int main() {
        // 创建被适配者对象
        struct Adaptee adaptee;
        adaptee.specificRequest = specificRequest;
    
        // 创建适配器对象
        struct Adapter adapter;
        adapter.target.request = request;
        adapter.adaptee = adaptee;
    
        // 使用适配器调用目标接口
        adapter.target.request(&(adapter.target));
    
        return 0;
    }
    
    • 输出结果
    被适配者的特定请求
    

    外观模式

    外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得子系统更容易使用。

    在C语言中,外观模式可以帮助简化复杂系统的使用,隐藏系统的复杂性,提供一个更加友好和简单的接口给客户端使用。外观模式通常包含以下几个角色:

    1. 外观(Facade):提供了一个简单的接口,用来访问子系统中的一群接口。外观模式通过将客户端的请求委派给子系统中的各个对象来实现这个接口。

    2. 子系统(Subsystems):实现了系统的各个功能,但是这些功能对客户端来说可能过于复杂。外观模式通过封装这些子系统,提供了一个更简单的接口供客户端使用。

    场景

    1. 复杂系统的简化接口:当一个系统由多个子系统组成,而客户端只需使用其中的一部分功能时,可以使用外观模式将这些子系统的复杂功能进行封装,提供一个简化的接口给客户端使用。

    2. 隐藏系统的复杂性:当一个系统的内部结构复杂,而客户端不需要了解系统的内部实现细节时,可以使用外观模式隐藏系统的复杂性,提供一个更加友好和简单的接口给客户端使用。

    3. 简化接口的调用流程:当一个系统的接口调用流程比较繁琐,需要依次调用多个接口时,可以使用外观模式将这些接口调用流程进行封装,提供一个更简单和直观的接口给客户端使用。

    4. 封装遗留系统:当一个系统的接口需要向后兼容或者需要与其他系统进行集成时,可以使用外观模式封装遗留系统的接口,提供一个统一的接口给客户端使用,从而降低系统的耦合度。

    5. 统一接口规范:当一个系统需要支持多种不同的接口规范时,可以使用外观模式将这些不同的接口规范进行统一,提供一个统一的接口给客户端使用,使得客户端可以无需关心不同接口规范的细节。

    示例

    #include 
    
    // 子系统1:CPU
    struct CPU {
        void (*run)(void); // 运行方法
    };
    
    // 子系统1的具体实现
    void cpu_run() {
        printf("CPU开始运行\n");
    }
    
    // 子系统2:内存
    struct Memory {
        void (*load)(void); // 加载方法
    };
    
    // 子系统2的具体实现
    void memory_load() {
        printf("内存加载数据\n");
    }
    
    // 子系统3:硬盘
    struct HardDrive {
        void (*read)(void); // 读取方法
    };
    
    // 子系统3的具体实现
    void harddrive_read() {
        printf("硬盘读取数据\n");
    }
    
    // 外观:计算机
    struct ComputerFacade {
        struct CPU cpu;
        struct Memory memory;
        struct HardDrive hardDrive;
        void (*start)(void); // 启动方法
    };
    
    // 外观的实现
    void start() {
        printf("计算机启动中...\n");
        // 启动计算机时,依次启动CPU、内存和硬盘
        cpu_run();
        memory_load();
        harddrive_read();
        printf("计算机启动完成\n");
    }
    
    int main() {
        // 创建外观对象
        struct ComputerFacade computerFacade;
        // 初始化外观对象的各个子系统
        computerFacade.cpu.run = cpu_run;
        computerFacade.memory.load = memory_load;
        computerFacade.hardDrive.read = harddrive_read;
        // 初始化外观对象的启动方法
        computerFacade.start = start;
    
        // 使用外观模式启动计算机
        computerFacade.start();
    
        return 0;
    }
    
    • 输出结果
    计算机启动中...
    CPU开始运行
    内存加载数据
    硬盘读取数据
    计算机启动完成
    

    享元模式

    享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在通过共享对象来最大限度地减少内存使用和提高性能。在享元模式中,将对象的状态分为内部状态(Intrinsic State)和外部状态(Extrinsic State),其中内部状态可以被多个对象共享,而外部状态需要根据场景而变化。

    1. 享元工厂(Flyweight Factory):负责创建和管理享元对象,通过共享已创建的享元对象来减少对象的创建数量。

    2. 享元接口(Flyweight Interface):定义了享元对象的接口,包括设置外部状态的方法等。

    3. 具体享元对象(Concrete Flyweight):实现了享元接口,包含了内部状态,并对外部状态进行了处理。

    4. 客户端(Client):通过享元工厂获取享元对象,并设置外部状态,实现具体的业务逻辑。

    场景

    1. 大量对象的共享:当系统中存在大量相似对象,并且这些对象可以共享部分状态时,可以使用享元模式来减少对象的创建数量,降低内存消耗。

    2. 对象的状态可分为内部状态和外部状态:当一个对象的状态可以分为内部状态(Intrinsic State)和外部状态(Extrinsic State),并且内部状态可以被多个对象共享时,可以使用享元模式来共享内部状态,减少对象的创建数量。

    3. 需要缓存对象的场景:当系统需要缓存一些常用的对象,并且这些对象可以共享部分状态时,可以使用享元模式来缓存这些对象,提高系统的性能。

    4. 系统需要保持独立的对象:当系统需要保持独立的对象,但又希望共享相同的状态时,可以使用享元模式来共享这些状态,同时保持对象的独立性。

    5. 需要减少系统中对象的数量:当系统中存在大量相似对象,并且这些对象可以共享部分状态时,可以使用享元模式来减少对象的创建数量,提高系统的性能和资源利用率。

    示例

    #include 
    #include 
    #include 
    
    // 享元对象结构体
    typedef struct {
        char *name; // 内部状态
        int size;   // 外部状态
    } Flyweight;
    
    // 享元工厂
    Flyweight *get_flyweight(char *name) {
        static Flyweight *flyweights[10] = {NULL}; // 预先创建的享元对象数组
        static int index = 0;
    
        // 查找现有的享元对象
        for (int i = 0; i < index; i++) {
            if (strcmp(flyweights[i]->name, name) == 0) {
                return flyweights[i];
            }
        }
    
        // 创建新的享元对象
        Flyweight *flyweight = (Flyweight *)malloc(sizeof(Flyweight));
        flyweight->name = strdup(name);
        flyweights[index++] = flyweight;
        return flyweight;
    }
    
    // 客户端使用享元对象
    void client(char *name, int size) {
        Flyweight *flyweight = get_flyweight(name);
        printf("对象名:%s,大小:%d\n", flyweight->name, size);
    }
    
    int main() {
        // 客户端使用享元对象
        client("object1", 10);
        client("object2", 20);
        client("object1", 30); // 复用现有的享元对象
        client("object3", 15);
        client("object2", 25); // 复用现有的享元对象
    
        return 0;
    }
    
    • 输出结果
    对象名:object1,大小:10
    对象名:object2,大小:20
    对象名:object1,大小:30
    对象名:object3,大小:15
    对象名:object2,大小:25
    

    装饰器模式

    装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向现有对象动态地添加新功能,同时又不改变其结构。装饰器模式通过创建一个包装器(Wrapper),将被装饰的对象进行包装,从而可以在运行时动态地添加新的功能。

    1. 组件接口(Component Interface):定义了被装饰对象和装饰器共同实现的接口。

    2. 具体组件(Concrete Component):实现了组件接口的具体对象,它是被装饰的对象。

    3. 装饰器(Decorator):实现了组件接口,并包含了一个指向被装饰对象的指针,它可以动态地添加新的功能。

    4. 具体装饰器(Concrete Decorator):实现了装饰器接口的具体对象,用于添加新的功能。

    场景

    1. 动态添加功能:当需要在不修改对象的基本结构的情况下,动态地添加额外的功能时,可以使用装饰器模式。这样可以避免修改现有的代码,使得系统更加灵活和可扩展。

    2. 多个功能的组合:当需要对对象的功能进行组合和排列,以满足不同的需求时,可以使用装饰器模式。通过组合不同的装饰器,可以实现不同的功能组合,而无需创建大量的子类。

    3. 功能的分层扩展:当需要对对象的功能进行分层扩展,以便于复用和维护时,可以使用装饰器模式。通过创建不同层次的装饰器,可以实现对对象功能的分层扩展,从而使得系统更易于理解和维护。

    4. 功能的独立扩展:当需要对对象的部分功能进行独立扩展和修改时,可以使用装饰器模式。通过创建独立的装饰器,可以实现对对象的部分功能进行扩展,而不影响其他功能的使用。

    5. 动态改变对象的行为:当需要动态地改变对象的行为,以适应不同的场景时,可以使用装饰器模式。通过在运行时动态地装饰对象,可以实现对对象行为的动态改变,从而提高系统的灵活性和可定制性。

    示例

    #include 
    
    // 组件接口
    typedef struct {
        void (*operation)(void);
    } Component;
    
    // 具体组件
    typedef struct {
        Component component;
    } ConcreteComponent;
    
    // 具体组件的操作实现
    void concrete_operation(void) {
        printf("执行具体组件的操作\n");
    }
    
    // 装饰器
    typedef struct {
        Component component;
        void (*decorate)(Component *);
    } Decorator;
    
    // 具体装饰器1
    typedef struct {
        Decorator decorator;
    } ConcreteDecorator1;
    
    // 具体装饰器1的装饰方法
    void decorate1(Component *component) {
        printf("在具体装饰器1中添加功能1\n");
        component->operation();
    }
    
    // 具体装饰器2
    typedef struct {
        Decorator decorator;
    } ConcreteDecorator2;
    
    // 具体装饰器2的装饰方法
    void decorate2(Component *component) {
        printf("在具体装饰器2中添加功能2\n");
        component->operation();
    }
    
    int main() {
        // 创建具体组件
        ConcreteComponent concreteComponent;
        concreteComponent.component.operation = concrete_operation;
    
        // 创建具体装饰器1,并装饰具体组件
        ConcreteDecorator1 concreteDecorator1;
        concreteDecorator1.decorator.component = concreteComponent.component;
        concreteDecorator1.decorator.decorate = decorate1;
        concreteDecorator1.decorator.decorate(&(concreteDecorator1.decorator.component));
    
        // 创建具体装饰器2,并装饰具体组件
        ConcreteDecorator2 concreteDecorator2;
        concreteDecorator2.decorator.component = concreteComponent.component;
        concreteDecorator2.decorator.decorate = decorate2;
        concreteDecorator2.decorator.decorate(&(concreteDecorator2.decorator.component));
    
        return 0;
    }
    
    • 输出结果
    在具体装饰器1中添加功能1
    执行具体组件的操作
    在具体装饰器2中添加功能2
    执行具体组件的操作
    

    组合模式

    组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式让客户端以统一的方式处理单个对象以及对象组合,从而使得客户端无需区分对象和对象组合。

    1. 组件接口(Component Interface):定义了组合中所有对象共同的接口,以便于客户端统一处理对象和对象组合。

    2. 叶子组件(Leaf Component):表示组合中的叶子节点,它实现了组件接口,但是没有子节点。

    3. 复合组件(Composite Component):表示组合中的复合节点,它实现了组件接口,并且包含了子节点。

    场景

    1. 文件系统:文件系统中的目录和文件可以使用组合模式来表示。目录可以看作是复合组件,它包含了多个文件或目录(叶子组件和复合组件),而文件可以看作是叶子组件,它不包含其他文件或目录。

    2. 图形用户界面(GUI):图形用户界面中的控件和容器可以使用组合模式来表示。控件可以看作是叶子组件,它显示具体的内容或功能,而容器可以看作是复合组件,它包含了多个控件(叶子组件和复合组件)。

    3. 组织架构:组织架构中的部门和员工可以使用组合模式来表示。部门可以看作是复合组件,它包含了多个员工(叶子组件和复合组件),而员工可以看作是叶子组件,它不包含其他员工。

    4. 菜单系统:菜单系统中的菜单项和菜单可以使用组合模式来表示。菜单项可以看作是叶子组件,它表示一个具体的功能或选项,而菜单可以看作是复合组件,它包含了多个菜单项(叶子组件和复合组件)。

    5. 物品组织:在游戏开发中,物品的组织结构可以使用组合模式来表示。物品可以看作是叶子组件,它表示一个具体的物品,而物品箱或背包可以看作是复合组件,它包含了多个物品(叶子组件和复合组件)。

    示例

    #include 
    
    // 定义组件类型
    typedef enum {
        LEAF,       // 叶子节点
        COMPOSITE   // 复合节点
    } NodeType;
    
    // 定义组件结构体
    typedef struct Node {
        NodeType type;          // 节点类型:叶子节点或复合节点
        char *name;             // 节点名称
        union {
            struct Node *child; // 叶子节点指向NULL,复合节点指向子节点链表的头节点
            struct Node *next;  // 复合节点链表中的下一个节点
        } next;
    } Node;
    
    // 打印节点名称
    void print_node(Node *node) {
        printf("%s\n", node->name);
    }
    
    // 打印树节点
    void print_tree(Node *root, int depth) {
        if (root == NULL) return;
    
        // 打印节点名称
        for (int i = 0; i < depth; ++i) {
            printf("  ");
        }
        print_node(root);
    
        // 递归打印子节点
        if (root->type == COMPOSITE) {
            print_tree(root->next.child, depth + 1);
        }
    
        // 递归打印兄弟节点
        print_tree(root->next.next, depth);
    }
    
    int main() {
        // 创建根节点
        Node root = {COMPOSITE, "根节点", .next = {NULL}};
    
        // 创建子节点
        Node node1 = {LEAF, "叶子节点1", .next = {NULL}};
        Node node2 = {LEAF, "叶子节点2", .next = {NULL}};
    
        // 创建复合节点
        Node composite_node = {COMPOSITE, "复合节点", .next = {.child = &node1}};
    
        // 将复合节点添加到根节点
        root.next.next = &composite_node;
        composite_node.next.next = &node2;
    
        // 打印树节点
        print_tree(&root, 0);
    
        return 0;
    }
    
    • 输出结果
    根节点
      复合节点
        叶子节点2
      叶子节点2
    复合节点
      叶子节点2
    叶子节点2
    
  • 相关阅读:
    HTML+CSS大作业:基于HMTL校园学校网页设计题材【我的学校网站】
    开发工程师必备————【Day12】MySQL数据库基础操作
    Redis 哨兵模式的原理及其搭建
    OceanBase 4.1解读:读写兼备的DBLink让数据共享“零距离”
    PyTorch安装以及VsCode重新连接矩池云时Tumx的使用问题
    WinForm的前世今生
    Java8中判断一个对象不为空存在一个类对象是哪个
    node开发时避免重复重启
    如何在 ACK 中使用 MSE Ingress
    动手学深度学习—— 1.引言
  • 原文地址:https://blog.csdn.net/weixin_42607526/article/details/139170819