生成器模式
可分步骤创建复杂对象,使得可以使用相同的创建代码生成不同的对象。(将变化部分的代码放在生成器中)
适用场景
1.在可选参数较多时
示例问题:现在需要建造一栋房屋,可以是简单房屋,也可以是豪华的房屋。
如果是通过一个包括所有可能参数的超级构造函数来创建房屋对象,那么对于简单房屋而言,构造函数中绝大部分的参数都没有使用,会使得对于构造函数的调用十分不简洁。
在支持方法重载的编程语言中,可以新建几个只有较少参数的简化版构造函数,但这些函数仍需调用最复杂的构造函数,传递默认值来替代省略的参数。
这时可以通过使用生成器模式将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。
无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。
2.在需要创建不同形式的产品时
可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。(通过生成器隔离变化与不变的代码)
如下图:同一建造步骤,通过生成器使用不同的建造材料产生不同房子。
3.在对象组成部分不确定时
在某些情况下,一个对象的组成部分不是一开始就可以确定的,需要分步骤进行:一边解析一边创建。
以下是 AWTK 加载 UI 界面的代码,通过一边解析二进制文件,一边通过生成器生成 UI 界面中的控件,并设置其控件属性。
ret_t ui_loader_load_default(ui_loader_t* loader, const uint8_t* data, uint32_t size,
ui_builder_t* b) {
rbuffer_t rbuffer;
widget_desc_t desc;
uint32_t magic = 0;
uint8_t widget_end_mark = 0;
return_value_if_fail(loader != NULL && data != NULL && b != NULL, RET_BAD_PARAMS);
return_value_if_fail(rbuffer_init(&rbuffer, data, size) != NULL, RET_BAD_PARAMS);
return_value_if_fail(rbuffer_read_uint32(&rbuffer, &magic) == RET_OK, RET_BAD_PARAMS);
return_value_if_fail(magic == UI_DATA_MAGIC, RET_BAD_PARAMS);
ui_builder_on_start(b);
while ((rbuffer.cursor + sizeof(desc)) <= rbuffer.capacity) {
const char* key = NULL;
const char* value = NULL;
return_value_if_fail(rbuffer_read_binary(&rbuffer, &desc, sizeof(desc)) == RET_OK,
RET_BAD_PARAMS);
ui_builder_on_widget_start(b, &desc);
return_value_if_fail(rbuffer_read_string(&rbuffer, &key) == RET_OK, RET_BAD_PARAMS);
while (*key) {
return_value_if_fail(rbuffer_read_string(&rbuffer, &value) == RET_OK, RET_BAD_PARAMS);
ui_builder_on_widget_prop(b, key, value);
return_value_if_fail(rbuffer_read_string(&rbuffer, &key) == RET_OK, RET_BAD_PARAMS);
}
ui_builder_on_widget_prop_end(b);
if (rbuffer_has_more(&rbuffer)) {
return_value_if_fail(rbuffer_peek_uint8(&rbuffer, &widget_end_mark) == RET_OK,
RET_BAD_PARAMS);
while (widget_end_mark == 0) {
rbuffer_read_uint8(&rbuffer, &widget_end_mark);
ui_builder_on_widget_end(b);
if ((rbuffer.cursor + 1) >= rbuffer.capacity ||
rbuffer_peek_uint8(&rbuffer, &widget_end_mark) != RET_OK) {
break;
}
}
}
if (b->widget == NULL) break;
}
ui_builder_on_end(b);
return RET_OK;
}
总结
优点:
- 可以简化构造函数,避免 “重叠构造函数” 的出现。
- 生成不同形式的产品时, 可以复用相同的制造代码。
- 可以分步创建对象,适用于对象组成部分不确定,需要边解析边创建。
- 单一职责原则,可以将每种产品生成代码抽取到同一位置,使得代码易于维护。
缺点:
- 代码整体复杂程度会有所增加。
参考
22种设计模式:refactoringguru.cn/design-patterns
AWTK:github.com/zlgopen/awtk
《设计模式:可复用面向对象软件的基础》