将对象组合成属性结构以表示“部分-整体”的层次结构。组合使得用户和单个对象和组合对象的使用具有一致性。
组合模式设计的角色:
1. Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component.
2. Leaf:在组合中表示叶子节点对象,叶子节点没有子节点。
3. Composite:定义树枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加和删除等。
举个简单例子(树枝和叶子)
1 Component
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2 Leaf(树叶)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
3 Composite(树枝)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
4 测试代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
输出结果:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
上面测试代码部分构建树结构的代码着实不太好看,效率太低。其实,在真实应用中。并不是这样子手工地构建一棵复杂的树,应该是我们已经将整棵树的节点内容、逻辑关系都存储在数据库表中,开发人员编程从数据库中的表中读取记录来构建整棵树。
透明模式 vs 安全模式
上面的代码属于透明模式,我们先看看安全模式是怎么实现的:
更改一下Component:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
再更改下Leaf
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
被称为安全模式是因为Leaf不具有add和remove等方法,这些具体方法是被下置到Composite类中去具体实现了。透明模式将add和remove等方法上升到抽象构建类Component中去了。那么此时Leaf类在具体实现时就必须将继承而来的add和remove等不可用、不合理的方法给注释掉(@Deprecated),并抛出适当的异常,不提供给用户使用。至于是使用透明模式还是安全模式就仁者见仁智者见智咯。不过,在这一模式中,相对于安全性,我们比较强调透明性。
使用场景:
- 用于对象的部分-整体层次结构,如树形菜单、文件夹菜单、部门组织架构等。
- 对用户隐藏组合对象与单个对象的不同,使得用户统一地使用组合结构中的所有对象。
优缺点
优点:使客户端调用简单,客户端可以一直的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。更容易在组合体内加入对象不见,客户端不必因为加入了新的对象不见而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利。
缺点:组合模式不容易限制组合中的构件。
Jdk中的组合模式
java.util.Map#putAll(Map)
java.util.List#addAll(Collection)
java.util.Set#addAll(Collection)
反面教材
回想起以前写页面菜单的时候,从数据库中读取类似的树形结构,当时没有采用这种组合模式,而是限定死树的最大深度(页面的菜单深度不超过3,实际上也没见到过超过3的),这样前端js代码在深度不超过3时没有问题,当超过3时,就需要重新编写页面菜单的js代码了,这样的扩展性并不好,如果当时采用了这种组合模式就会好很多。
写页面菜单已经是6年前的事了,代码早已不详~最近用项目需求要写一棵树,单例模式的。大伙来看看有什么不妥之处。
首先定义实体类Entry
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
定义树节点(这里的叶子节点可以看成son==null的TreeNode,而树枝节点son!=null)
- 1
- 2
- 3
- 4
- 5
- 6
定义单例树节点
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
测试代码(根-集群-主机-虚拟机的层次关系):
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
大伙比对组合模式来看看这段代码有什么欠妥之处,欢迎在下方留言探讨。
参考资料
1. 《23种设计模式》
2. 《细数JDK里的设计模式》
3. 《组合模式(Composite)的安全模式与透明模式》