组合模式是什么
组合模式(Composite Pattern),又叫部分整体模式,作为结构型模式,组合模式是用于把一组相似的对象当作一个单一的对象。组合多个对象形成树形结构来表示“整体-部分”的关系层次,它创建了对象组的树形结构。
组合模式能干什么
它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
使用场景
部分、整体场景,如树形菜单,文件、文件夹的管理。
组合模式优缺点
优点
1、高层模块调用简单:组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2、节点自由增加:更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,
缺点
1、在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
2、设计较复杂,客户端需要花更多时间理清类之间的层次关系;
3、不容易限制容器中的构件;
组合模式的结构
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
辅助图示
树状辅助图
合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。

示例代码图示
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。
示例代码
1、抽象构件
public abstract class Component {
abstract void add(Component c);
abstract void remove(Component c);
abstract Component getChild(int i);
abstract int getNumber();
abstract void operation();
}
2、容器节点
import java.util.ArrayList;
public class CompositeEmployee extends Component{
private String name;
private String dept;
private String position;
private int salary;
private ArrayList<Component> componentArrayList;
public CompositeEmployee(String name, String dept, String position, int salary) {
this.name = name;
this.dept = dept;
this.position = position;
this.salary = salary;
this.componentArrayList = new ArrayList<>(16);
}
@Override
void add(Component c) {
if (!componentArrayList.contains(c)) componentArrayList.add(c);
}
@Override
void remove(Component c) {
if (componentArrayList.contains(c)) componentArrayList.remove(c);
}
@Override
Component getChild(int i) {
return componentArrayList.get(i) == null ? null : componentArrayList.get(i);
}
@Override
int getNumber() {
return componentArrayList.size();
}
/**
* 在组合模式结构中,由于容器构件中仍然可以包含容器构件,
* 因此在对容器构件进行处理时需要使用递归算法,
* 即在容器构件的operation()方法中递归调用其成员构件的operation()方法。
*/
@Override
void operation() {
if (position.equals("CEO")) this.salary += 2000;
for (Object obj : componentArrayList) {
Component component = (Component) obj;
if(component instanceof CompositeEmployee) {
((CompositeEmployee) component).setSalary(((CompositeEmployee) component).getSalary() + 1000);
((Component) obj).operation();
}else {
((LeafEmployee) component).setSalary(((LeafEmployee) component).getSalary() + 500);
}
//do somthin
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
@Override
public String toString() {
return ("[ 姓名 : "+ name
+", 所属部门 : "+ dept + ", " +
"职位 :"+ position + ", " +
"薪水 :" + salary+" ]");
}
}
3、叶子节点
import java.io.PrintStream;
public class LeafEmployee extends Component{
private String name;
private String dept;
private String position;
private int salary;
public LeafEmployee(String name, String dept, String position, int salary) {
this.name = name;
this.dept = dept;
this.position = position;
this.salary = salary;
}
@Override
void add(Component c) {
//do somthing
}
@Override
void remove(Component c) {
//do somthing
}
@Override
Component getChild(int i) {
//do somthing
return null;
}
@Override
int getNumber() {
//do somthing
return 0;
}
@Override
void operation() {
//do somthing
System.out.printf("我是员工 %d, 所属部门 %d, 薪水是 %d", name, dept, salary);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
@Override
public String toString() {
return (" [ 姓名 : "+ name
+", 所属部门 : "+ dept + ", " +
"职位 :"+ position + ", " +
"薪水 :" + salary+" ]");
}
}
4、Client
public class TestClient {
public static void main(String[] args) {
//定义CEO
Component componentCEO =
new CompositeEmployee("John", "", "CEO", 40000);
//定义TeamLeader
Component componentTeamLealderOfJavaBackend =
new CompositeEmployee("Robert", "JavaBackend", "TL", 25000);
Component componentTeamLealderOfFrontEnd =
new CompositeEmployee("Michel", "FrontEnd", "TL",25000);
//定义普通员工
Component componentFrontEndEmployee1 = new LeafEmployee("Bob", "FrontEnd", "Employee", 8000);
Component componentFrontEndEmployee2 = new LeafEmployee("Laura", "FrontEnd", "Employee",9000);
Component componentJavaBackend1 = new LeafEmployee("Richard", "JavaBackend", "Employee",9000);
Component componentJavaBackend2 = new LeafEmployee("Rob", "JavaBackend", "Employee",9000);
//对员工进行装载
componentCEO.add(componentTeamLealderOfJavaBackend);
componentCEO.add(componentTeamLealderOfFrontEnd);
componentTeamLealderOfJavaBackend.add(componentJavaBackend1);
componentTeamLealderOfJavaBackend.add(componentJavaBackend2);
componentTeamLealderOfFrontEnd.add(componentFrontEndEmployee1);
componentTeamLealderOfFrontEnd.add(componentFrontEndEmployee2);
//打印所有员工
System.out.println(componentCEO);
for (int i = 0; i < componentCEO.getNumber(); i++) {
System.out.println(componentCEO.getChild(i));
for (int i1 = 0; i1 < componentCEO.getChild(i).getNumber(); i1++) {
System.out.println(componentCEO.getChild(i).getChild(i1));
}
}
System.out.println("----------------------------------------------");
componentCEO.operation();
//为所有员工调薪一次
System.out.println(componentCEO);
for (int i = 0; i < componentCEO.getNumber(); i++) {
System.out.println(componentCEO.getChild(i));
for (int i1 = 0; i1 < componentCEO.getChild(i).getNumber(); i1++) {
System.out.println(componentCEO.getChild(i).getChild(i1));
}
}
}
}
运行结果
[ 姓名 : John, 所属部门 : , 职位 :CEO, 薪水 :40000 ]
[ 姓名 : Robert, 所属部门 : JavaBackend, 职位 :TL, 薪水 :25000 ]
[ 姓名 : Richard, 所属部门 : JavaBackend, 职位 :Employee, 薪水 :9000 ]
[ 姓名 : Rob, 所属部门 : JavaBackend, 职位 :Employee, 薪水 :9000 ]
[ 姓名 : Michel, 所属部门 : FrontEnd, 职位 :TL, 薪水 :25000 ]
[ 姓名 : Bob, 所属部门 : FrontEnd, 职位 :Employee, 薪水 :8000 ]
[ 姓名 : Laura, 所属部门 : FrontEnd, 职位 :Employee, 薪水 :9000 ]
----------------------------------------------
[ 姓名 : John, 所属部门 : , 职位 :CEO, 薪水 :42000 ]
[ 姓名 : Robert, 所属部门 : JavaBackend, 职位 :TL, 薪水 :26000 ]
[ 姓名 : Richard, 所属部门 : JavaBackend, 职位 :Employee, 薪水 :9500 ]
[ 姓名 : Rob, 所属部门 : JavaBackend, 职位 :Employee, 薪水 :9500 ]
[ 姓名 : Michel, 所属部门 : FrontEnd, 职位 :TL, 薪水 :26000 ]
[ 姓名 : Bob, 所属部门 : FrontEnd, 职位 :Employee, 薪水 :8500 ]
[ 姓名 : Laura, 所属部门 : FrontEnd, 职位 :Employee, 薪水 :9500 ]