大话设计模式C语言之组合模式
组合模式
组合模式 (CompositePattern) 是一种结构型设计模式,将对象组合成树状结构以表示“整体/部分”层次关系,使得用户对单个对象和组合对象的使用具有一致性,组合模式以递归方式处理对象树中的所有项目。
案例代码
组件部分
组件 (Component)接口描述了树中简单项目和复杂项目所共有的操作,实现所有类共有接口的默认行为。在递归组合树时,组件接口使得我们可以在树中忽略单个对象和组合对象之间的区别。
组件接口 component.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #ifndef COMPONENT_H #define COMPONENT_H
#include <stddef.h>
typedef struct Component Component;
typedef struct ComponentVTable { void (*destroy)(Component *self); void (*print)(Component *self, int depth); size_t (*get_size)(Component *self); int (*add)(Component *self, Component *child); int (*remove)(Component *self, Component *child); } ComponentVTable;
struct Component { const ComponentVTable *vtable; };
void component_destroy(Component *self); void component_print(Component *self, int depth); size_t component_get_size(Component *self); int component_add(Component *self, Component *child); int component_remove(Component *self, Component *child);
#endif
|
组件实现 component.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include "component.h"
void component_destroy(Component *self) { if (self && self->vtable && self->vtable->destroy) self->vtable->destroy(self); }
void component_print(Component *self, int depth) { if (self && self->vtable && self->vtable->print) self->vtable->print(self, depth); }
size_t component_get_size(Component *self) { if (self && self->vtable && self->vtable->get_size) return self->vtable->get_size(self); return 0; }
int component_add(Component *self, Component *child) { if (self && self->vtable && self->vtable->add) return self->vtable->add(self, child); return -1; }
int component_remove(Component *self, Component *child) { if (self && self->vtable && self->vtable->remove) return self->vtable->remove(self, child); return -1; }
|
叶节点
叶节点(Leaf)是树的基本结构,它不包含子项目。一般情况下叶节点最终会完成大部分的实际工作,因为它们无法将工作指派给其他部分。
叶结点接口 file.h
1 2 3 4 5 6 7 8 9
| #ifndef FILE_H #define FILE_H
#include "component.h"
Component *file_create(const char *name, size_t size);
#endif
|
叶结点实现 file.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include "file.h" #include <stdlib.h> #include <string.h> #include <stdio.h>
typedef struct { Component base; char *name; size_t size; } File;
static void file_destroy(Component *self) { File *file = (File *)self; free(file->name); free(file); }
static void file_print(Component *self, int depth) { File *file = (File *)self;
for (int i = 0; i < depth; ++i) printf(" ");
printf("- %s (%zu bytes)\n", file->name, file->size); }
static size_t file_get_size(Component *self) { File *file = (File *)self; return file->size; }
static const ComponentVTable file_vtable = { .destroy = file_destroy, .print = file_print, .get_size = file_get_size, .add = NULL, .remove = NULL };
Component *file_create(const char *name, size_t size) { if (!name) return NULL;
File *file = calloc(1, sizeof(File)); if (!file) return NULL;
file->name = strdup(name); if (!file->name) { free(file); return NULL; }
file->size = size;
file->base.vtable = &file_vtable;
return (Component *)file; }
|
容器/组合
容器(Container)——又名 “组合(Composite)”——是包含叶节点或其他容器等子项目的单位。容器不知道其子项目所属的具体类,它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目,处理中间结果,然后将最终结果返回给客户端。
容器接口 directory.h
1 2 3 4 5 6 7 8 9
| #ifndef DIRECTORY_H #define DIRECTORY_H
#include "component.h"
Component *directory_create(const char *name);
#endif
|
容器实现 directory.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| #include "directory.h" #include <stdlib.h> #include <string.h> #include <stdio.h>
#define INITIAL_CAPACITY 4
typedef struct { Component base; char *name; Component **children; size_t count; size_t capacity; } Directory;
static int directory_expand(Directory *dir) { size_t new_cap = dir->capacity * 2; Component **tmp = realloc(dir->children, new_cap * sizeof(Component *)); if (!tmp) return -1;
dir->children = tmp; dir->capacity = new_cap; return 0; }
static void directory_destroy(Component *self) { Directory *dir = (Directory *)self;
for (size_t i = 0; i < dir->count; ++i) component_destroy(dir->children[i]);
free(dir->children); free(dir->name); free(dir); }
static void directory_print(Component *self, int depth) { Directory *dir = (Directory *)self;
for (int i = 0; i < depth; ++i) printf(" ");
printf("+ %s\n", dir->name);
for (size_t i = 0; i < dir->count; ++i) component_print(dir->children[i], depth + 1); }
static size_t directory_get_size(Component *self) { Directory *dir = (Directory *)self; size_t total = 0;
for (size_t i = 0; i < dir->count; ++i) total += component_get_size(dir->children[i]);
return total; }
static int directory_add(Component *self, Component *child) { if (!child) return -1;
Directory *dir = (Directory *)self;
if (dir->count == dir->capacity) { if (directory_expand(dir) != 0) return -1; }
dir->children[dir->count++] = child; return 0; }
static int directory_remove(Component *self, Component *child) { Directory *dir = (Directory *)self;
for (size_t i = 0; i < dir->count; ++i) { if (dir->children[i] == child) {
component_destroy(child);
for (size_t j = i; j + 1 < dir->count; ++j) dir->children[j] = dir->children[j + 1];
dir->count--; return 0; } }
return -1; }
static const ComponentVTable directory_vtable = { .destroy = directory_destroy, .print = directory_print, .get_size = directory_get_size, .add = directory_add, .remove = directory_remove };
Component *directory_create(const char *name) { if (!name) return NULL;
Directory *dir = calloc(1, sizeof(Directory)); if (!dir) return NULL;
dir->name = strdup(name); if (!dir->name) { free(dir); return NULL; }
dir->capacity = INITIAL_CAPACITY;
dir->children = calloc(dir->capacity, sizeof(Component *)); if (!dir->children) { free(dir->name); free(dir); return NULL; }
dir->base.vtable = &directory_vtable;
return (Component *)dir; }
|
测试客户端代码 main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include "directory.h" #include "file.h" #include <stdio.h>
int main(void) { Component *root = directory_create("root"); Component *file1 = file_create("a.txt", 100); Component *file2 = file_create("b.txt", 200);
Component *subdir = directory_create("subdir"); Component *file3 = file_create("c.txt", 300);
component_add(subdir, file3);
component_add(root, file1); component_add(root, file2); component_add(root, subdir);
component_print(root, 0);
printf("\nTotal size: %zu bytes\n",component_get_size(root));
component_destroy(root); while (1); return 0; }
|
总结
如果应用的核心模型能用树状结构表示,在应用中使用组合模式才有价值。符合开闭原则。无需更改现有代码就可以在应用中添加新元素,使其成为对象树的一部分。但对于功能差异较大的类,提供公共接口或许会有困难。在特定情况下你需要过度一般化组件接口,使其变得令人难以理解。