概述
将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次
涉及角色:
- omponent
抽象构件角色:这是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。 - Leaf
叶子构件 :叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。 - Composite
树枝构件:定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关的操作,如增加(add)和删除(remove)等。
组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因
UML
使用场景
- 需求中是体现部分与整体层次结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了,如树形菜单,文件、文件夹的管理
优点
- 高层模块调用简单:组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。将”客户代码与复杂的对象容器结构“解耦。
- 节点自由增加:可以更容易地往组合对象中加入新的构件。
缺点
- 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
- 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系
代码示例
透明式组合模式
在Component中声明所有来管理子对象的方法,其中包括add,remove等。这样实现Component接口的所有子类都具备了add和remove方法。这样做的好处是叶节点和枝节点对于外界没有区别,它们具备完全一致的接口
package com.designpattern.Composite;
public abstract class ComponentLucency {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
protected abstract void add(ComponentLucency c);
protected abstract void remove(ComponentLucency c);
protected abstract void display(int depth);
}
package com.designpattern.Composite;
import java.util.ArrayList;
import java.util.List;
public class CompositeLucency extends ComponentLucency {
private List<ComponentLucency> children = new ArrayList<>();
public CompositeLucency(String name){
setName(name);
}
@Override
protected void add(ComponentLucency c) {
children.add(c);
}
@Override
protected void remove(ComponentLucency c) {
children.remove(c);
}
@Override
protected void display(int depth) {
String temp = "";
for (int i=0; i<depth; i++){
temp += "-";
}
System.out.println(temp + getName());
for (ComponentLucency child : children){
child.display(depth + 2);
}
}
}
package com.designpattern.Composite;
public class LeafLucency extends ComponentLucency {
public LeafLucency(String name){
setName(name);
}
@Override
protected void add(ComponentLucency c) {
//叶子节点不能添加子节点,因此不做实现
}
@Override
protected void remove(ComponentLucency c) {
//叶子节点不能添加子节点,因此不做实现
}
@Override
protected void display(int depth) {
String temp = "";
for (int i=0; i<depth; i++){
temp += "-";
}
System.out.println(temp + getName());
}
}
弊端:客户端对叶节点和枝节点是一致的,但叶节点并不具备add和remove的功能,因而对它们的实现是没有意义的
安全式组合模式
在Component中不去声明add和remove方法,那么子类的Leaf就不需要实现它,而是在Composit声明所有用来管理子类对象的方法
package com.designpattern.Composite;
public abstract class ComponentSafety {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
protected abstract void display(int depth);
}
package com.designpattern.Composite;
import java.util.ArrayList;
import java.util.List;
public class CompositeSafety extends ComponentSafety {
private List<ComponentSafety> children = new ArrayList<>();
public CompositeSafety(String name){
setName(name);
}
protected void add(ComponentSafety c) {
children.add(c);
}
protected void remove(ComponentSafety c) {
children.remove(c);
}
@Override
protected void display(int depth) {
String temp = "";
for (int i=0; i<depth; i++){
temp += "-";
}
System.out.println(temp + getName());
for (ComponentSafety child : children){
child.display(depth + 2);
}
}
}
package com.designpattern.Composite;
public class LeafSafety extends ComponentSafety {
public LeafSafety(String name){
setName(name);
}
@Override
protected void display(int depth) {
String temp = "";
for (int i=0; i<depth; i++){
temp += "-";
}
System.out.println(temp + getName());
}
}
叶节点无需在实现add与remove这样的方法,但是对于客户端来说,必须对叶节点和枝节点进行判定,为客户端的使用带来不便
测试主函数
package com.designpattern.Composite;
public class TestMain {
public static void main(String[] args) {
//透明式组合模式
ComponentLucency rootLucency = new CompositeLucency("根节点");
ComponentLucency rootChildX = new CompositeLucency("根节点的X节点");
ComponentLucency rootChildY = new CompositeLucency("根节点的Y节点");
rootLucency.add(rootChildX);
rootLucency.add(rootChildY);
rootChildX.add(new LeafLucency("X节点-叶子"));
rootChildX.add(new LeafLucency("X节点-叶子"));
rootChildY.add(new LeafLucency("Y节点—叶子"));
rootLucency.display(1);
//安全式组合模式
CompositeSafety rootSafe = new CompositeSafety("Root");
CompositeSafety branchX = new CompositeSafety("Branch X in Root");
CompositeSafety branchY = new CompositeSafety("Branch Y in Root");
rootSafe.add(branchX);
rootSafe.add(branchY);
branchX.add(new LeafSafety("Leaf A in Branch X"));
branchX.add(new CompositeSafety("Leaf B in Branch X"));
branchY.add(new LeafSafety("Leaf C in Branch Y"));
rootSafe.display(1);
}
}