组合模式(Composite)
定义
由于事物与事物之前存在某种关系,进而组织起来并形成某种结构并且可以共同发挥作用。组合模式又叫做"部分-整体"模式,它是一种将对象组合成树状的层次结构的模式,用来表示"部分-整体"的关系,使用户对单个对象和组合对象具有一致的访问性。
理解
桥接模式是指将不同的资源组合在一起构成丰富的功能。
例如:有一堆彩色的画笔和一堆各种形状的图案,通过两两组合(桥接),就可以形成不同色彩的图案。
组合模式是将存在某种关系的相似资源组织起来,形成部分和整体有一致性的模式。
例如:如果仔细观察叶子会发现又有小的叶子,一个小的叶子上又有更小的枝叶。我们不管从宏观还是微观维度上看都是类似的结构。好似存在着某种大自然的规律,类似的结构总在重复、迭代地显现出某种自似性。(仔细观察下图片)
代码
这其实牵扯到一个数学概念:分形理论。不管是连绵的山川、飘浮的云朵、岩石的断裂口、树冠、花菜、还是人类的大脑皮层……把这些部分与整体以某种方式相似的形体呈现出来就称为分形。从简单到复杂,或是复杂到简单,我们抽出任意一个“部分”,其与“整体”的结构是类似的。这里我们就拿类似树结构的文件系统目录结构来举例。
我们可以看到,从根目录开始,下面包含文件和文件夹,文件夹下面有包含子文件夹和子文件,而文件属于“叶子”节点,下面不再有延续分支。这里模糊文件和文件夹的区别,我们统一抽象为“节点”。
//Node 节点
type Node interface {
//添加后续节点
Add(child Node)
//space 表示打印名字前,有几个空格
Print(space int)
}
根据节点接口定义,实现文件类。文件类不能再添加后续节点,所以Add
中什么也不做,Print
负责打印文件名称,根据space决定打印名字前,控制几个空格。
type File struct {
name string
}
//Add
func (t *File) Add(child Node) {
//do nothing
}
//Print
func (t *File) Print(space int) {
for i := 0; i fmt.Print(" ")
}
fmt.Println(t.name)
}
根据接口定义,实现文件夹类。区别在于文件夹下,可以包含子文件和子文件夹,所以额外定义了child []Node
切片。Add
负责追加Node节点到child中,Print
负责打印名称,区别在于space传入下级目录的Print
时,需要自增+1
type Folder struct {
name string
child []Node
}
//Add add file or add folder
func (t *Folder) Add(child Node) {
t.child = append(t.child, child)
}
//Print
func (t *Folder) Print(space int) {
for i := 0; i fmt.Print(" ")
}
fmt.Println(t.name)
//传入下级目录
space++
for _, v := range t.child {
v.Print(space)
}
}
调用范例:
func main() {
//根目录D盘
diver := Folder{name: "D:"}
//两个文件
f1 := File{name: "1.txt"}
f2 := File{name: "test.txt"}
//文件夹
fd1 := Folder{name: "文档"}
ff1 := File{name: "2.txt"}
ff2 := File{name: "3.txt"}
ff3 := File{name: "4.txt"}
fd1.Add(&ff1)
fd1.Add(&ff2)
fd1.Add(&ff3)
//追加到D文件夹中
diver.Add(&f1)
diver.Add(&f2)
diver.Add(&fd1)
//打印目录层次
diver.Print(0)
return
}
输出:
可以看到组合模式,其实就是相似的资源(文件或文件夹)互相递归叠加,最后组成一个庞大的文件系统结构。再比如生物体,先从单细胞生物,到多细胞生物,再到高级生物,其部分(单个细胞)与整体(生命个体)结构或其行为总是以类似的形式涌现,分形之道如此,组合模式亦是如此。
源代码地址:
https://github.com/gofish2020/gopattern/tree/main/structure/composite