大话设计模式C语言之享元模式

享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。

案例代码

本案例中每个字符 glyph 是享元对象(Flyweight),字符的字体、大小、粗细 是内部状态(intrinsic state)字符的位置 是外部状态(extrinsic state),享元由 FlyweightFactory 管理并复用。使用 opaque pointer 完全隐藏 struct。

享元

享元(Flyweight)类包含原始对象中部分能在多个对象中共享的状态。同一享元对象可在许多不同情景中使用。享元中存储的状态被称为 “内在状态”。传递给享元方法的状态被称为 “外在状态”。

享元接口 flyweight.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef FLYWEIGHT_FACTORY_H
#define FLYWEIGHT_FACTORY_H

#include "flyweight.h"

typedef struct FlyweightFactory FlyweightFactory;

/* 创建 */
FlyweightFactory *flyweight_factory_create(void);

/* 获取享元 */
Flyweight *flyweight_factory_get(FlyweightFactory *factory, char symbol, const char *font, int size, int bold);

/* 销毁 */
void flyweight_factory_destroy(FlyweightFactory *factory);

#endif

享元实现 flyweight.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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "flyweight.h"

/* 内部结构 */
struct Flyweight
{
char symbol;

char *font;
int size;
int bold;
};

/* 安全字符串复制(嵌入式友好) */
static char *strdup_safe(const char *s)
{
if (!s)
return NULL;

size_t len = strlen(s) + 1;

char *copy = malloc(len);
if (copy)
memcpy(copy, s, len);

return copy;
}

Flyweight *flyweight_create(char symbol, const char *font, int size, int bold)
{
Flyweight *fw = malloc(sizeof(Flyweight));
if (!fw)
return NULL;

fw->symbol = symbol;
fw->font = strdup_safe(font);
fw->size = size;
fw->bold = bold;

return fw;
}

void flyweight_render(Flyweight *self, const GlyphContext *context)
{
if (!self || !context)
return;

printf("Render '%c' font=%s size=%d bold=%d at (%d,%d)\n",
self->symbol,
self->font,
self->size,
self->bold,
context->x,
context->y);
}

char flyweight_get_symbol(Flyweight *self)
{
if (!self)
return 0;

return self->symbol;
}

void flyweight_destroy(Flyweight *self)
{
if (!self)
return;

free(self->font);
free(self);
}

享元工厂

享元工厂(Flyweight Factory)会对已有享元的缓存池进行管理。有了工厂后,客户端就无需直接创建享元,它们只需调用工厂并向其传递目标享元的一些内在状态即可。工厂会根据参数在之前已创建的享元中进行查找,如果找到满足条件的享元就将其返回;如果没有找到就根据参数新建享元。

flyweight_factory.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef FLYWEIGHT_FACTORY_H
#define FLYWEIGHT_FACTORY_H

#include "flyweight.h"

typedef struct FlyweightFactory FlyweightFactory;

/* 创建 */
FlyweightFactory *flyweight_factory_create(void);

/* 获取享元 */
Flyweight *flyweight_factory_get(FlyweightFactory *factory, char symbol, const char *font, int size, int bold);

/* 销毁 */
void flyweight_factory_destroy(FlyweightFactory *factory);

#endif

flyweight_factory.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
#include <stdlib.h>
#include <string.h>
#include "flyweight_factory.h"

/* 最大数量(嵌入式可配置) */
#define MAX_FLYWEIGHTS 128

struct FlyweightFactory
{
Flyweight *pool[MAX_FLYWEIGHTS];
int count;
};

FlyweightFactory *flyweight_factory_create(void)
{
FlyweightFactory *factory =
calloc(1, sizeof(FlyweightFactory));

return factory;
}

static int match(Flyweight *fw,
char symbol,
const char *font,
int size,
int bold)
{
if (!fw)
return 0;

return
flyweight_get_symbol(fw) == symbol;
}

/* 获取或创建 */
Flyweight *flyweight_factory_get(FlyweightFactory *factory,
char symbol,
const char *font,
int size,
int bold)
{
if (!factory)
return NULL;

/* 查找已有 */
for (int i = 0; i < factory->count; i++)
{
if (match(factory->pool[i], symbol, font, size, bold))
{
return factory->pool[i];
}
}

/* 创建新对象 */
if (factory->count >= MAX_FLYWEIGHTS)
return NULL;

Flyweight *fw =
flyweight_create(symbol, font, size, bold);

if (!fw)
return NULL;

factory->pool[factory->count++] = fw;

return fw;
}

void flyweight_factory_destroy(FlyweightFactory *factory)
{
if (!factory)
return;

for (int i = 0; i < factory->count; i++)
{
flyweight_destroy(factory->pool[i]);
}

free(factory);
}

外部状态

情景(Context)类包含原始对象中各不相同的外在状态。情景与享元对象组合在一起就能表示原始对象的全部状态。通常情况下原始对象的行为会保留在享元类中。因此调用享元方法必须提供部分外在状态作为参数。但你也可将行为移动到情景类中,然后将连入的享元作为单纯的数据对象。

glyph_context.h
1
2
3
4
5
6
7
8
9
10
11
#ifndef GLYPH_CONTEXT_H
#define GLYPH_CONTEXT_H

typedef struct
{
int x;
int y;
} GlyphContext;

#endif

测试客户端代码 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
30
31
32
33
34
35
#include <stdio.h>
#include "glyph_context.h"
#include "flyweight_factory.h"

int main(void)
{
FlyweightFactory *factory = flyweight_factory_create();

GlyphContext ctx1 = {10, 20};
GlyphContext ctx2 = {30, 40};
GlyphContext ctx3 = {50, 60};

Flyweight *a1 = flyweight_factory_get(factory, 'A', "Arial", 12, 0);

Flyweight *a2 = flyweight_factory_get(factory, 'A', "Arial", 12, 0);

Flyweight *b1 = flyweight_factory_get(factory, 'B', "Arial", 12, 0);

/* 渲染 */
flyweight_render(a1, &ctx1);
flyweight_render(a2, &ctx2);
flyweight_render(b1, &ctx3);

/* 验证复用 */
if (a1 == a2)
{
printf("A reused\n");
}

flyweight_factory_destroy(factory);

while (1);
return 0;
}

总结

   享元模式只是一种优化。在应用该模式之前,你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题,并且确保该问题无法使用其他更好的方式来解决。