组合模式的定义:又叫作整体-部分(Part-Whole)模式,通过将单个对象(叶节点)和组合对象用相同的接口表示,使客户端对单个对象和组合对象的访问具有一致性。它是一种将对象组合成树状的层次结构的模式。属于结构型设计模式。
组合模式一般用来描述整体与部分的关系,它将对象组合到树状结构,顶层的节点被称为根节点,最末级的节点成为叶节点,中间的节点成为树枝节点,树形结构如下图。
由上图可以看出,根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶节点与树枝节点在语义上不属于同一种类型,但是在组合模式中,我们会把树枝节点和叶节点看作同一种数据类型(用统一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象都属于同一种类型,客户端不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给客户端的使用带来极大的便利。
组合模式的结构:组合模式包含以下3个角色。
- 抽象根节点(Component):为叶节点和树枝节点声明公共接口,并实现它们的默认行为。
- 树叶节点(Leaf):组合中的叶节点对象,它没有子节点,是树状结构的最末级。
- 树枝节点(Composite):组合中的分支节点对象,它有子节点,主要作用是存储和管理子节点。
组合模式的实现:组合模式分为透明式组合模式和安全式组合模式。我们以公司组织架构为例,分别用透明式组合模式和安全式组合模式实现。
某公司组织架构如下图,公司包括销售部、研发部和财务部,销售部和财务部下又分为A组和B组。
透明组合模式的实现:
在透明组合模式中,抽象根节点声明了所有子类中的全部方法,客户端无须区别叶节点和树枝节点,它们具备一致的接口,对客户端来说是透明的。但其缺点是:叶节点本来没有增加 Add()、删除Remove() 及 获取子节点GetChild() 的方法,却要实现它们(空实现或抛出异常),这样会带来一些安全性问题。
//抽象根节点
public interface DeptComponent {
void add(DeptComponent deptComponent);
void remove(DeptComponent deptComponent);
List<DeptComponent> getChildren();
void getName();
}
//叶节点
public class LeafDept implements DeptComponent {
private String name;
public LeafDept(String name){
this.name = name;
}
@Override
public void add(DeptComponent deptComponent) { }
@Override
public void remove(DeptComponent deptComponent) { }
@Override
public List<DeptComponent> getChildren() {
return null;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//树枝节点
public class CompositeDept implements DeptComponent{
private List<DeptComponent> children = new ArrayList<>();
private String name;
CompositeDept(String name){
this.name = name;
}
@Override
public void add(DeptComponent deptComponent) {
children.add(deptComponent);
}
@Override
public void remove(DeptComponent deptComponent) {
children.remove(deptComponent);
}
@Override
public List<DeptComponent> getChildren() {
return children;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//测试类
public class CompositeTest {
public static void main(String[] args) {
DeptComponent company = new CompositeDept("某公司");
DeptComponent saleDept = new CompositeDept("销售部");
DeptComponent developmentDept = new CompositeDept("研发部");
DeptComponent financeDept = new CompositeDept("财务部");
DeptComponent saleA = new LeafDept("销售部A组");
DeptComponent saleB = new LeafDept("销售部B组");
DeptComponent developmentA = new LeafDept("研发部A组");
DeptComponent developmentB = new LeafDept("研发部B组");
developmentDept.add(developmentA);
developmentDept.add(developmentB);
saleDept.add(saleA);
saleDept.add(saleB);
company.add(saleDept);
company.add(developmentDept);
company.add(financeDept);
List<DeptComponent> children = company.getChildren();
children.stream().forEach(deptComponent -> {
deptComponent.getName();
deptComponent.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
});
}
}
透明组合模式的结构图:
安全组合模式的实现:
在安全组合模式中,将管理叶节点的方法移到树枝节点中,抽象根节点和叶节点没有对子对象的管理方法,避免了透明组合模式的安全性问题,但由于叶节点和树枝节点有不同的接口,客户端在调用时要知道叶节点和树枝节点的存在,所以失去了透明性。
//抽象根节点
public interface DeptComponent {
void getName();
}
//叶节点
public class LeafDept implements DeptComponent {
private String name;
public LeafDept(String name){
this.name = name;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//树枝节点
public class CompositeDept implements DeptComponent{
private List<DeptComponent> children = new ArrayList<>();
private String name;
CompositeDept(String name){
this.name = name;
}
public void add(DeptComponent deptComponent) {
children.add(deptComponent);
}
public void remove(DeptComponent deptComponent) {
children.remove(deptComponent);
}
public List<DeptComponent> getChildren() {
return children;
}
@Override
public void getName() {
System.out.println(name+"前来报到!");
}
}
//测试类
public class CompositeTest {
public static void main(String[] args) {
CompositeDept company = new CompositeDept("某公司");
CompositeDept saleDept = new CompositeDept("销售部");
CompositeDept developmentDept = new CompositeDept("研发部");
CompositeDept financeDept = new CompositeDept("财务部");
DeptComponent saleA = new LeafDept("销售部A组");
DeptComponent saleB = new LeafDept("销售部B组");
DeptComponent developmentA = new LeafDept("研发部A组");
DeptComponent developmentB = new LeafDept("研发部B组");
developmentDept.add(developmentA);
developmentDept.add(developmentB);
saleDept.add(saleA);
saleDept.add(saleB);
company.add(saleDept);
company.add(developmentDept);
company.add(financeDept);
List<DeptComponent> children = company.getChildren();
children.stream().forEach(deptComponent -> {
deptComponent.getName();
if(deptComponent instanceof CompositeDept){
CompositeDept compositeDept = (CompositeDept) deptComponent;
compositeDept.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
}
});
}
}
安全组合模式的结构图:
组合模式的优点:
- 组合模式使客户端处理单个对象和组合对象逻辑一致,这简化了客户端代码;
- 更容易在组合体内加入新的对象,满足“开闭原则”;
组合模式的缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
组合模式的使用场景:
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对客户端隐藏组合对象与单个对象的差异,使客户端可以用统一的接口使用组合结构中的所有对象的场合。