Composite 组合模式
意图
将对象组合为树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
知道广义表吗?如果你了解广义表,应该就可以很好的理清楚组合模式的定义。
在广义表中,一个表内的元素即可以是一个基础元素,也可以是另一个表的表头,也就是说,表本身作为一个元素储存在另一个表中,是一种递归的储存结构。
图形化的广义表如下所示,大写字母表示表头,小写字母表示基础元素。
mermaid 图画起来不像我预期那样啊O(∩_∩)O
如上图所示,表A不仅包含了b,v 两个基础元素,也包含了C,D两个表头元素,这两个表中仍然可以递归地包含其他表头(F,G仍然是表头元素!)。
这就是Composite模式的基本逻辑:它把表和表元素看做是一种东西,从而在访问时能够一体看待,因此更加方便快捷。就像树一样,表是树的普通节点,而基础元素就是那些叶子节点,叶子和根在这种设计中是平行关系。
从稳定-变化的角度来看,元素和表本身的访问方式应当是类似的,这是稳定点,而表的基础元素可以是任何东西。
代码案例
直接上代码:这里是广义表的一种模拟实现。
class Component
{
private:
Component* com;
Component* next;
public:
virtual Component* head() = 0;
virtual Component* tail() = 0;
}
首先是抽象基类:这一基类包含了一个指向自己的指针, 和类似链表一样指向“下一个”元素的指针。
head()函数返回广义表的表头元素,而tail函数将广义表的除表头元素之外的所有元素打包成一个表返回。如果com是Leaf, 那么com是空指针。否则,com会指向它自己代表的表。而next则是表内元素向后传递。
Component本身应当带有一个值,这里我们只关注它的结构,因此就忽略这一点。
下面是基础元素(Leaf)和表(Composite)。
class Leaf
{
Leaf(Composite*c):com(c){
...}//这里应当有初始化叶子节点的代码
Component* head()
{
return com->value;//其实应当有一个值,但我们这里图方便省略了
}
Component* tail()
{
return nullptr;//其实leaf的tail没有意义
}
}
class Composite
{
Composite(Composite*c):com(c){
...}//这里应当有初始化表的代码
Component* head()
{
return com;//直接返回表头即可
}
Component* tail()
{
return com->next;
}
}
可以看到,这样的类关系可以让我们在访问表时忽略表本身的属性,而单单注重它的结构。对表本身值的访问被封装到了head和tail两个函数中。
这也就是Composite模式的最大好处。
解释
这也是为什么我把Composite归类为数据结构类的设计模式:它本身就很像是一种特殊的数据结构。
客户在操作这种结构时,只需要把所有元素都看做Component, 而不需要在乎它是值还是另一个Composite。这使得客户可以一致地使用组合结构和单个对象,简化了用户代码。
很多时候,比如,在家里,几只圆珠笔装在笔筒里,它们的关系似乎是笔在笔筒中。但如果放在超市里,笔和笔筒不过是平行的两种商品罢了。看上去分属不同类层次的对象实际上在某种情况下应当被平行处理时,这种结构就派上用场了。
Qt中的Widget类,Java中的Object, 看上去包含一切的基类,就有一种Composite模式的意味在里面:这种设计使得看上去截然不同的对象在有些时刻可以一起处理。
Composite模式是一种非常常用的设计模式。
总结
设计模式 | Composite(组合模式) |
---|---|
稳定点: | Leaf和Composite的访问方式类似 |
变化点: | Leaf本身值的类型 |
效果: | 使得不同层级的对象得以被平行处理 |
特点: | 递归定义,递归操作 |
类图:
2021.3.1转载请标明出处