大话设计模式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;

/* ---------- virtual methods ---------- */

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
};

/* ---------- constructor ---------- */

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;

/* ---------- helpers ---------- */

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;
}

/* ---------- virtual methods ---------- */

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
};

/* ---------- constructor ---------- */

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;
}

总结

   如果应用的核心模型能用树状结构表示,在应用中使用组合模式才有价值。符合开闭原则。无需更改现有代码就可以在应用中添加新元素,使其成为对象树的一部分。但对于功能差异较大的类,提供公共接口或许会有困难。在特定情况下你需要过度一般化组件接口,使其变得令人难以理解。