组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
简单的理解就是一颗树中有根节点也有叶子节点,对于它们的增删改查,我们都使用名字同样的函数,但是具体的行为却因为注册时不一样而不一样。
看下代码:
package composite
import "fmt"
type Component interface {
Parent() Component
SetParent( Component)
Name() string
SetName(string)
AddChild(component Component)
Print( string)
}
const (
LeafNode = iota
CompositeNode
)
//组合模式的简单工厂,可以看成根据参数决定创建目录,或者文件
//返回即可以描述目录又可以描述文件的interface
func NewComponent(kind int, name string) Component {
var c Component
switch kind {
case LeafNode:
c = NewLeaf()
case CompositeNode:
c = NewComposite()
}
c.SetName(name)
return c
}
//这个是共有部分,叶子和根节点都有这个,细节的不同可以重写相关方法函数
type component struct {
parent Component
name string
}
func (c *component) Parent() Component {
return c.parent
}
func (c *component) SetParent(parent Component) {
c.parent = parent
}
func (c *component) Name() string {
return c.name
}
func (c *component) SetName(name string) {
c.name = name
}
func (c *component) AddChild(component Component) {
}
func (c *component) Print(string) {
}
type Leaf struct {
component
}
func NewLeaf() *Leaf {
return &Leaf{}
}
//文件,重写一下Print函数,因为它与目录不一样
func (c *Leaf)Print(pre string) {
fmt.Printf("%s#%s\n", pre, c.Name())
}
type Composite struct {
component
childs []Component
}
func NewComposite() *Composite {
return &Composite{
childs: make([]Component, 0),
}
}
//目录才会执行AddChild
func (c *Composite)AddChild(child Component) {
child.SetParent(c)
c.childs = append(c.childs, child)
}
//目录的Print除了打印目录名字,还要遍历它下面所有的文件。
func (c *Composite)Print(pre string) {
fmt.Printf("%s-%s\n", pre, c.Name())
pre += " "
for _, comp := range c.childs {
comp.Print(pre)
}
}
客户端
package composite
import "testing"
func TestCompsite(t *testing.T) {
root := NewComponent(CompositeNode, "root")
c1 := NewComponent(CompositeNode, "c1")
c2 := NewComponent(CompositeNode, "c2")
c3 := NewComponent(CompositeNode, "c3")
l1 := NewComponent(LeafNode, "l1")
l2 := NewComponent(LeafNode, "l2")
l3 := NewComponent(LeafNode, "l3")
root.AddChild(c1)
root.AddChild(c2)
c1.AddChild(c3)
c1.AddChild(l1)
c2.AddChild(l2)
c2.AddChild(l3)
root.Print("")
}
结果:
=== RUN TestCompsite
-root
-c1
-c3
#l1
-c2
#l2
#l3
--- PASS: TestCompsite (0.00s)
PASS
组合模式的优缺点
优点:
- 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
- 将”客户代码与复杂的对象容器结构“解耦。
- 可以更容易地往组合对象中加入新的构件。
缺点: 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。
注意的问题:
- 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存。
- 客户端尽量不要直接调用树叶类中的方法(在我上面实现就是这样的,创建的是一个树枝的具体对象;),而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。
组合模式的使用场景
在以下情况下应该考虑使用组合模式:
- 当想表达对象的部分-整体的层次结构时。
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
.NET 中Winform 中的空间类型大多用到了该种设计模式。另, 《设计模式》一书中提倡:相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决