定义
组合模式(Composite Pattern)属于结构型模式,将对象组合成树形结构以表示“部分”—“整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
什么叫“部分”—“整体”的层次结构呢,举个例子:
假如我们在网上购物买了很多的东西,仓库收到订单后按照订单明细给我们的东西打包,由于东西很多他们用了很多的包装盒,并最终用一个巨大的盒子来包裹每一个订单的商品,如下图所示:
每一个盒子里面都可能包含许多的小盒子和商品,以此类推。因此“部分”—“整体”是一个相对的概念,假如在第三层的包装盒内部还有许多的小盒子和商品,那么这个第三层的包装盒对于整个订单的包裹来说他是“部分”,而对于他里面的盒子与商品来说,他又是“整体”。
再比如我们电脑中的文件夹里面,A文件夹里面包含了B、C、D文件夹以及一个demo.txt文件,B文件夹里面又包括了E、F文件夹和demo.ps和demo.word文件。这也是“部分”—“整体”的层次结构。
结构图
解析:
- component :一个接口或抽象类,描述了树中简单项目和复杂项目所共有的操作。
- leaf:叶节点是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
- composite:是包含叶节点或其他容器等子项目的单位。 容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
实例
现在,假设我们要建立一个公司的销售部门层次结构。我们先把这个层次结构画一下:
然手直接上代码:
我们先建一个部门的接口Department:
package com.design_pattern.composite;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public interface Department {
void printDepartmentName(); //输出部门名称的方法
}
然后建立一个总的销售部门:SaleDepartment
package com.design_pattern.composite;
import java.util.ArrayList;
import java.util.List;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public class SaleDepartment implements Department {
//部门名称
private String name;
//相当于一个Composite,下面可以拥有其他的子部门,而不是一个叶子节点
List<Department> departments = new ArrayList<>();
//用于增加子部门
public void addDepartment(Department department){
departments.add(department);
}
//删除子部门
public void remove(Department department){
departments.remove(department);
}
@Override
public void printDepartmentName() {
//打印本部门的名称
System.out.println(name);
//打印本部门下的子部门的名称
for (Department department:departments){
department.printDepartmentName();
}
}
public SaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建一个国内销售部门:DomesticSaleDepartment
package com.design_pattern.composite;
import java.util.ArrayList;
import java.util.List;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public class DomesticSaleDepartment implements Department {
private String name;
//相当于一个Composite,下面可以拥有其他各个省份的子部门
List<Department> list = new ArrayList<>();
public DomesticSaleDepartment(String name) {
this.name = name;
}
//用于增加子部门
public void add(Department department){
list.add(department);
}
//用于删除子部门
public void remove(Department department){
list.remove(department);
}
@Override
public void printDepartmentName() {
System.out.println(name);
for (Department department:list){
department.printDepartmentName();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
建立一个国际销售部门:InternationalSaleDepartment ,我们假设国外业务不多,就不细分了,作为一个叶子节点
package com.design_pattern.composite;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public class InternationalSaleDepartment implements Department {
private String name;
public InternationalSaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void printDepartmentName() {
System.out.println(name);
}
}
在建立一个浙江销售部门:ZheJiangSaleDepartment ,同样作为叶子节点
package com.design_pattern.composite;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public class ZheJiangSaleDepartment implements Department {
private String name;
public ZheJiangSaleDepartment(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void printDepartmentName() {
System.out.println(name);
}
}
测试类:CompositeDemo
package com.design_pattern.composite;
/**
* Created on 2020/3/20
* Package com.design_pattern.composite
*
* @author dsy
*/
public class CompositeDemo {
public static void main(String[] args) {
//创建一个总的销售部门作为根节点
Department saleDepartment = new SaleDepartment("销售部门");
//创建国内销售部门
DomesticSaleDepartment domesticSaleDepartment = new DomesticSaleDepartment("国内销售部门");
//创建国内浙江销售部门
ZheJiangSaleDepartment ZheJiangSaleDepartment = new ZheJiangSaleDepartment("浙江销售部门");
//将浙江销售部门加入到国内销售部门
domesticSaleDepartment.list.add(ZheJiangSaleDepartment);
//创建国际销售部门
InternationalSaleDepartment internationalSaleDepartment = new InternationalSaleDepartment("国际销售部门");
//将国内销售部门和国外销售部门全部加入到总的销售部门去
((SaleDepartment) saleDepartment).departments.add(domesticSaleDepartment);
((SaleDepartment) saleDepartment).departments.add(internationalSaleDepartment);
//打印部门名称
saleDepartment.printDepartmentName();
}
}
打印输出:
销售部门
国内销售部门
浙江销售部门
国际销售部门
总结
使用场合
- 需求中体现的是部分与整体层次的结构时,如实现树状结构,可以使用组合模式
- 希望用户可以忽略组合对象与单个对象的不同,统一的使用组合结构中的所有对象时,就应该考虑用组合模式了
优点
- 可以利用多态和递归机制更方便地使用复杂树结构
- 符合开闭原则,无需更改现有代码,就可以在应用中添加新元素,使其成为对象树的一部分
缺点
- 对于功能差异较大的类, 提供公共接口或许会有困难