Composite(组合模式)总结
前言
软软件设计模式(Design pattern),简称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。--来自百度百科。既然设计模式有那么多好处,我们在做程序设计的时候,就应该充分考虑自己需要解决的问题是否有一个设计模式与之相似,尽量使用现有的解决方案来设计程序,避免代码重复或自己考虑不足导致设计缺陷。
本周五参加了设计模式研讨会,讨论的主题就是组合模式,这篇博文的目的是对组合模式的总结,以及分享一个组合模式的C语言应用例子,希望对想学习这个模式的同学有所帮助。
一、组合模式基础知识总结
1、模式意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。2、参与者
Component--为组合中的对象声明接口;
--在适当的情况下,实现所有类共有接口的缺省行为;
--声明一个接口用于访问和管理Component的子组件。
Leaf
-- 在组合中表示叶节点对象,叶节点没有子节点;在组合中定义图元对象的行为。
Composite
-- 定义有子部件的那些部件的行为;
--存储子部件;
--在Component接口中实现与子部件有关的操作。
Client
-- 通过Component接口操作组合部件的对象。
3、结构及效果
结构图: 效果:1、定义了包含基本对象和组合对象的类层次结构。
2、简化客户代码,客户代码对叶子节点和组合节点的使用方式是一致的。
3、使得更容易增加新类型的组件。
4、使你的设计变得更加一般化。
4、适用性
1、你想表示对象的部分-整体层次结构。2、你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象。
二、组合模式在AWKT中的应用
[AWTK](https://github.com/zlgopen/awtk) 全称 Toolkit AnyWhere,是 ZLG 开发的开源 GUI 引擎,旨在为嵌入式系统、WEB、各种小程序、手机和 PC 打造的通用 GUI 引擎,为用户提供一个功能强大、高效可靠、简单易用、可轻松做出炫酷效果的 GUI 引擎。在AWTK中用到了组合模式,widget_t相当于Component,具体的控件如button是叶子节点,而window相当于Composite。 结构图: widget_t的定义部分内容如下,类中包含一个包含一个保存子控件的容器对象children:
struct _widget_t {
/**
* @property {xy_t} x
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* x坐标(相对于父控件的x坐标)。
*/
xy_t x;
/**
* @property {xy_t} y
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* y坐标(相对于父控件的y坐标)。
*/
xy_t y;
/**
* @property {wh_t} w
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* 宽度。
*/
wh_t w;
/**
* @property {wh_t} h
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* 高度。
*/
wh_t h;
/**
* @property {char*} name
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* 控件名字。
*/
char* name;
/**
* @property {char*} pointer_cursor
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* 鼠标光标图片名称。
*/
char* pointer_cursor;
/**
* @property {char*} tr_text
* @annotation ["set_prop","get_prop","readable","persitent","design","scriptable"]
* 保存用于翻译的字符串。
*/
char* tr_text;
...
/**
* @property {darray_t*} children
* @annotation ["readable"]
* 全部子控件。
*/
darray_t* children;
...
widget_t在组合模式中应包含的一些接口管理子控件的接口:添加、删除、查找和获取子控件的接口;
基本操作接口:绘制控件等。
/**
* @method widget_add_child
* 加入一个子控件。
*
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {widget_t*} child 子控件对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t widget_add_child(widget_t* widget, widget_t* child);
/**
* @method widget_remove_child
* 移出指定的子控件(并不销毁)。
*
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {widget_t*} child 子控件对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t widget_remove_child(widget_t* widget, widget_t* child);
/**
* @method widget_insert_child
* 插入子控件到指定的位置。
*
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {uint32_t} index 位置序数(大于等于总个数,则放到最后)。
* @param {widget_t*} child 子控件对象。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t widget_insert_child(widget_t* widget, uint32_t index, widget_t* child);
/**
* @method widget_restack
* 调整控件在父控件中的位置序数。
*
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {uint32_t} index 位置序数(大于等于总个数,则放到最后)。
*
* @return {ret_t} 返回RET_OK表示成功,否则表示失败。
*/
ret_t widget_restack(widget_t* widget, uint32_t index);
/**
* @method widget_child
* 查找指定名称的子控件(同widget_lookup(widget, name, FALSE))。
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {const char*} name 子控件的名称。
*
* @return {widget_t*} 子控件或NULL。
*/
widget_t* widget_child(widget_t* widget, const char* name);
/**
* @method widget_lookup
* 查找指定名称的子控件(返回第一个)。
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {const char*} name 子控件的名称。
* @param {bool_t} recursive 是否递归查找全部子控件。
*
* @return {widget_t*} 子控件或NULL。
*/
widget_t* widget_lookup(widget_t* widget, const char* name, bool_t recursive);
/**
* @method widget_lookup_by_type
* 查找指定类型的子控件(返回第一个)。
* @annotation ["scriptable"]
* @param {widget_t*} widget 控件对象。
* @param {const char*} type 子控件的名称。
* @param {bool_t} recursive 是否递归查找全部子控件。
*
* @return {widget_t*} 子控件或NULL。
*/
widget_t* widget_lookup_by_type(widget_t* widget, const char* type, bool_t recursive);
ret_t widget_on_paint_background(widget_t* widget, canvas_t* c);
/**
* @method widget_on_paint_self
* 绘制自身。
* @param {widget_t*} widget 控件对象。
* @param {canvas_t*} c canvas对象。
*
* @return {ret_t} 返回。
*/
ret_t widget_on_paint_self(widget_t* widget, canvas_t* c);
/**
* @method widget_on_paint_children
* 绘制子控件。
* @param {widget_t*} widget 控件对象。
* @param {canvas_t*} c canvas对象。
*
* @return {ret_t} 返回。
*/
ret_t widget_on_paint_children(widget_t* widget, canvas_t* c);
/**
* @method widget_on_paint_border
* 绘制边框。
* @param {widget_t*} widget 控件对象。
* @param {canvas_t*} c canvas对象。
*
* @return {ret_t} 返回。
*/
ret_t widget_on_paint_border(widget_t* widget, canvas_t* c);
...
绘制函数的具体实现代码如下:
static ret_t widget_paint_impl(widget_t* widget, canvas_t* c) {
int32_t ox = widget->x;
int32_t oy = widget->y;
uint8_t save_alpha = c->global_alpha;
if (widget->opacity < TK_OPACITY_ALPHA) {
canvas_set_global_alpha(c, (widget->opacity * save_alpha) / 0xff);
}
if (widget->astyle != NULL) {
ox += style_get_int(widget->astyle, STYLE_ID_X_OFFSET, 0);
oy += style_get_int(widget->astyle, STYLE_ID_Y_OFFSET, 0);
}
canvas_translate(c, ox, oy);
widget_on_paint_begin(widget, c);
widget_on_paint_background(widget, c);
widget_on_paint_self(widget, c);
widget_on_paint_children(widget, c);
widget_on_paint_border(widget, c);
widget_on_paint_end(widget, c);
canvas_untranslate(c, ox, oy);
if (widget->opacity < TK_OPACITY_ALPHA) {
canvas_set_global_alpha(c, save_alpha);
}
widget_on_paint_done(widget, c);
return RET_OK;
}
从基类的接口实现发现,在组合模式中,叶子节点和组合节点都需要实现以下几个函数完成绘图,如果没有实现,则使用默认实现:widget_on_paint_begin
widget_on_paint_background
widget_on_paint_self
widget_on_paint_children
widget_on_paint_border
widget_on_paint_end
对于button这样的叶子控件,没有子节点;window控件包含子节点,在绘制window的时候,也需要递归绘制子控件。