1、定义

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

2、模式结构

组合(Composite)模式 - 图1

组合模式由三部分组成:

  • Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
  • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
  • Conposite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

3、实例

3.1 Component(抽象构件)

  1. public abstract class Component {
  2. public String getName() {
  3. throw new UnsupportedOperationException("不支持获取名称操作");
  4. }
  5. public void add(Component component) {
  6. throw new UnsupportedOperationException("不支持添加操作");
  7. }
  8. public void remove(Component component) {
  9. throw new UnsupportedOperationException("不支持删除操作");
  10. }
  11. public void print() {
  12. throw new UnsupportedOperationException("不支持打印操作");
  13. }
  14. public String getContent() {
  15. throw new UnsupportedOperationException("不支持获取内容操作");
  16. }
  17. }

3.2 文件夹类Folder(容器构件)

  1. public class Folder extends Component {
  2. private String name;
  3. private List<Component> componentList = new ArrayList<>();
  4. private int level;
  5. public Folder(String name) {
  6. this.name = name;
  7. }
  8. @Override
  9. public String getName() {
  10. return name;
  11. }
  12. @Override
  13. public void add(Component component) {
  14. componentList.add(component);
  15. }
  16. @Override
  17. public void remove(Component component) {
  18. componentList.remove(component);
  19. }
  20. @Override
  21. public void print() {
  22. System.out.println(getName());
  23. if (level == 0) {
  24. level = 1;
  25. }
  26. String prefix = "";
  27. for (int i = 0; i < level; i++) {
  28. prefix += "\t- ";
  29. }
  30. for (Component component : componentList) {
  31. if (component instanceof Folder) {
  32. ((Folder)component).level = level + 1;
  33. }
  34. System.out.print(prefix);
  35. component.print();
  36. }
  37. level = 0;
  38. }
  39. }

3.3 文件类File(叶子构件)

  1. public class File extends Component {
  2. private String name;
  3. private String content;
  4. public File(String name, String content) {
  5. this.name = name;
  6. this.content = content;
  7. }
  8. @Override
  9. public String getName() {
  10. return name;
  11. }
  12. @Override
  13. public void print() {
  14. System.out.println(getName());
  15. }
  16. @Override
  17. public String getContent() {
  18. return content;
  19. }
  20. }

3.4 客户端调用

  1. public class Client {
  2. public static void main(String[] args) {
  3. Folder DSFolder = new Folder("设计模式资料");
  4. File note1 = new File("组合模式.md", "组合模式组合多个对象形成树形结构以表示具有\"整体-部分\"关系的层次结构");
  5. File note2 = new File("工厂方法模式.md", "工厂方法模式定义一个用于创建对象的接口,让子类决定将哪一个类实例化。");
  6. DSFolder.add(note1);
  7. DSFolder.add(note2);
  8. Folder codeFolder = new Folder("样例代码");
  9. File readme = new File("README.md", "# 设计模式示例代码项目");
  10. Folder srcFolder = new Folder("src");
  11. File code = new File("组合模式示例.java", "这是组合模式的示例代码");
  12. srcFolder.add(code);
  13. codeFolder.add(readme);
  14. codeFolder.add(srcFolder);
  15. DSFolder.add(codeFolder);
  16. DSFolder.print();
  17. }
  18. }

4、适用场景

  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
  • 在一个使用面向对象开发的系统中需要处理一个树形结构。
  • 在一个系统中能够分离叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

5、在JDK集合的应用

组合(Composite)模式 - 图2

6、优缺点

6.1 优点
  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复制的树形结构,但对树形结构的控制却非常简单。

6.2 缺点
  • 使得设计更加复杂,客户端需要花更多时间理清类之间的层次关系。
  • 增加新构件时很难对容器中的构件类型进行限制。

7、安全方式VS透明方式

  • 安全方式在抽象组件中只定义一些默认的行为或属性,它是把容器节点和叶子节点彻底分开;透明方式是把用来组合使用的方法放到抽象类中,不管叶子对象和容器对象都有相同的结构,通过判断确定是叶子节点还是容器节点,如果处理不当,这个会在运行期出现问题,不是很建议的方式。
  • 安全方式与依赖倒置原则冲突;透明方式的好处就是它基本遵循了依赖倒置原则,方便系统进行扩展。
  • 安全方式在遍历树形结构的时候需要进行强制类型转换;在透明方式下,遍历整个树形结构是比较容易的,不用进行强制类型转换。