1、简介
组合模式(Composite Pattern)是一种结构型设计模式,其目的是将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
2、结构
组合模式包含三个角色:
- 抽象组件(Component):它是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件
- 叶子组件(Leaf):它在组合结构中表示叶节点对象,叶子组件没有子组件
- 组合组件(Composite):它在组合结构中表示分支节点对象,它有子组件
组合模式常用于构建树形结构,如文件和文件夹管理器、组织结构图、组织架构等。
3、优缺点
组合模式的优点:
- 它允许客户端将单个对象和组合对象一视同仁。
- 新增和删除子节点非常方便。
- 使客户端代码变得简单,因为客户端可以处理单个对象和组合对象一视同仁。
组合模式的缺点:
- 由于组合模式中会使用到大量的递归,所以在处理大量数据时会导致程序运行速度变慢。
- 由于组合模式中每个组件都会继承抽象组件,所以类的数量会增加。
4、使用场景
组合模式的适用场景:
- 在需要表示一个对象整体或部分层次结构时,使用组合模式。
- 当您需要支持在合适的时候增加新构件的能力时。
组合模式有很多变体,如树形组件、透明组合模式、安全组合模式等。组合模式在实际应用中非常常见,如文件和文件夹管理器、组织结构图、组织架构等。
实际应用:
通常用于构建树形结构:
- 文件和文件夹管理系统:文件和文件夹都可以看作一个节点,文件夹中可以包含文件和其他文件夹,这样就形成了一个树形结构。
- 组织结构图:组织构成树形结构,部门和员工都可以看作一个节点,部门可以包含其他部门和员工,这样就形成了一个树形结构。
5、代码实现
组合模式通常使用抽象类或接口来定义组件类。
Java代码实现步骤如下:
5.1、抽象组件(Component)
首先,我们可以定义一个抽象组件类,如下:
1. public abstract class Component { 2. protected String name; 3. 4. public Component(String name) { 5. this.name = name; 6. } 7. 8. public abstract void add(Component component); 9. public abstract void remove(Component component); 10. public abstract void display(int depth); 11. }
这个类包含了对子组件的管理方法,如add、remove和display。
然后,我们可以定义叶子组件类和容器组件类。叶子组件类不能再包含子组件,而容器组件类可以包含子组件。
5.2、叶子组件(Leaf)
叶子组件类示例:
1. public class Leaf extends Component { 2. public Leaf(String name) { 3. super(name); 4. } 5. 6. @Override 7. public void add(Component component) { 8. System.out.println("Cannot add to a leaf"); 9. } 10. 11. @Override 12. public void remove(Component component) { 13. System.out.println("Cannot remove from a leaf"); 14. } 15. 16. @Override 17. public void display(int depth) { 18. StringBuilder sb = new StringBuilder(); 19. for (int i = 0; i < depth; i++) { 20. sb.append("-"); 21. } 22. System.out.println(sb.toString() + name); 23. } 24. }
5.3、组合组件(Composite)
容器组件类示例:
1. import java.util.ArrayList; 2. import java.util.List; 3. 4. public class Composite extends Component { 5. private List<Component> children = new ArrayList<>(); 6. 7. public Composite(String name) { 8. super(name); 9. } 10. 11. @Override 12. public void add(Component component) { 13. children.add(component); 14. } 15. 16. @Override 17. public void remove(Component component) { 18. children.remove(component); 19. } 20. 21. @Override 22. public void display(int depth) { 23. StringBuilder sb = new StringBuilder(); 24. for (int i = 0; i < depth; i++) { 25. sb.append("-"); 26. } 27. System.out.println(sb.toString() + name); 28. 29. for (Component component : children) { 30. component.display(depth + 2); 31. } 32. } 33. }
5.4、client
客户端可以使用相同的方法来管理叶子组件和容器组件。
例如,在下面的示例中,我们可以创建一个根容器组件,并在其中添加叶子组件和其他容器组件:
1. Component root = new Composite("root"); 2. Component branch1 = new Composite("branch1"); 3. root.add(branch1); 4. root.add(new Leaf("leaf1")); 5. 6. Component branch2 = new Composite("branch2"); 7. branch1.add(new Leaf("leaf2")); 8. branch1.add(branch2); 9. branch2.add(new Leaf("leaf3")); 10. 11. root.display(1);
5.5、demo->end
最终结果:
5.6、代码总结
组合模式允许客户端以统一的方式处理个别对象和组合对象,并且可以使组合对象和个别对象具有相同的层次结构。这种模式对于构建树形结构很有用。
这只是组合模式的一种简单的实现方式,在实际项目中,根据需要可能需要对组件类进行更多的抽象和拓展。
6、深入理解代码案例
6.1、树状图
上述代码的树状图如下:
6.2、debug模式分析
6.2.1、打上断点
对第五行代码打上断点,开始debug,如下:
6.2.2、Component root = new Composite("root");
此时进入Composite类,准备调父类Component的有参构造器,代码如下:
6.2.2.1、super(name);
此时把"root"赋值给name,即Component root对象此时拥有name属性"root"
6.2.3、Component branch1 = new Composite("branch1");
创建对象流程与上面的root相同,此处不再说明
6.2.4、children集合
private List<Component> children = new ArrayList<>();
此时可以明显得知,root对象中包含了两个对象:branch1和left1
(此处要注意,left1是叶子,即它没有子组件)
6.2.4.1、Composite对象和Leaf对象的add()的区别
可以看到,Composite对象把传进来的Component对象添加进了集合中,但是Leaf对象并没有。
这符合叶子节点不再创建子组件的原则。
此处的区别,后面会再作说明
6.2.5、Component branch2 = new Composite("branch2");
此时创建branch2对象,这是目前第三个Composite对象。
然后可以看到,接下来,
branch1添加了两个对象:leaf2和branch2,
而branch2又添加了一个leaf3叶子对象
6.2.6、梳理关系脉络
此时各个对象之间的关系已经明朗,可以得到一开始的树形图。
需要注意的是,每new一个Composite对象,都自身背后会new一个ArrayList集合来存放该Composite对象的子节点对象,即:
root->branch1、leaf1
branch1->leaf2、branch2
branch2->leaf3
下一步即是多次递归操作!
6.2.7、最后一步 : 递归
最终由root对象调递归函数!
6.2.7.1、display(int depth)
递归函数来自Composite类重写的父类Componet递归方法:
这是一个简单的代码实现案例,所以此处的递归也只是作举例示用。该递归方法主要思路是获取调用递归方法的对象的集合(children),然后用foreach遍历,在遍历中不断找子对象。
但是如果获取递归方法的对象是Leaf叶子对象,则不会再往下创建子组件,即children集合中没有对象
6.2.7.2、root
root调用display方法后,显然控制台会输出"-root",由上面的叙述可以知道,root中的集合包含两个对象
6.2.7.3、root->branch1
此时由branch1对象,调用递归方法,需要注意的是,这时候的depth为:1+2=3
显然,此时控制台输出的是"---branch1",这时候branch1包含两个对象:leaf2和branch2。
由之前的代码执行顺序来看,leaf2对象比branch2先创建,则此时增强for循环中的第一个对象,应该是leaf2。此时由leaf2调用递归方法,需要注意的是,此时的depth为:3+2=5。
所以此时控制台接着输出的是"-----leaf2"由于Leaf2是叶子组件,则不再创建子组件,回到for循环。
6.2.7.4、root->branch1->branch2
然后接着遍历来到branch1的第二个对象:branch2。此时的depth依然为5,显然,此时控制台接着输出的是"-----branch2"。
branch2对象中包含了一个叶子组件:leaf3。
此时进入for循环,有且仅有一次循环!此时的depth为:5+2=7。
6.2.7.5、root->branch1->branch2->leaf3
此时控制台接着输出的是"-------leaf2",叶子节点结尾,没有再任何其他组件生成,至此root的左子树全部走完,逐层回退,退回第一步for循环遍历root对象中的集合的时候,如图:
此时遍历root中第二个对象leaf1,leaf1为叶子节点,此时的depth为3(同branch1时一样),则控制台最终输出结果为"---leaf1"
至此,debug完成,root的左右子树全部走完,递归结束
6.3、总结
虽然代码复杂度较高,但是该模式可以在运行时动态地组合组件,并对整棵树进行遍历。可以在运行时动态地组合组件,并对整棵树进行遍历。它适用于反映对象整体。