Author:Gorit
Date:2021年10月7日
Refer:《图解设计模式》

11.1 Composite 模式

在计算机中,有“文件夹”的概念(在有些操作系统中会成为 “目录”)。文件夹既可以存放文件,也可以存放其他的“文件夹”,在子文件夹中可以继续放文件以及子文件夹。这样文件夹就形成了一种容器结构,递归结构。

这样的话,文件和文件夹就是不同类型的对象,但是它们都“可以被放置在文件夹中”。文件和文件夹有时候又被统称为“目录条目”(directory entry)。在目录条目中,,文件夹 和 文件被当做同一种对象看待(一致性)

例如:我们查找一个文件 或者 目录,其实查找的都是 目录条目

因此,将文件夹与文件都作为目录条目看待一样,将容器和内容作为同一种东西看待,可以方便帮助我们处理问题。

在容器中既可以放入内容,也可以放入小容器,然而小容器中,又可以放置更小的容器。这样形成的容器结构,递归结构

总之:
Composite 模式就是这样的模式,能够使容器与内容一致性,创造出递归结构

11.2 示例程序

这里 Entry 即代表 “目录条目”,这样就实现了 File 和 Directory 类的一致性。

名字 说明
Entry 抽象类
File 表示文件的类
Directory 表示文件夹的类
FileTreatmentException 表示向文件中增加 Entry 时发生异常的类
Main 测试行为

Entry 抽象类

  1. package Composite;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/7
  5. * @desc 表示目录条目的抽象类, File 和 Directory 为其子类
  6. **/
  7. public abstract class Entry {
  8. public abstract String getName(); // 获取名字
  9. public abstract int getSize(); // 获取大小
  10. /**
  11. * 向文件夹中放入 文件 或 文件夹,该功能由子类 Directory 实现
  12. * @param entry
  13. * @return
  14. */
  15. public Entry add(Entry entry) { // 加入目录条目
  16. throw new FileTreatmentException();
  17. }
  18. public void printList() {
  19. printList("");
  20. }
  21. /**
  22. * 显示文件夹内容的一览
  23. * @param prefix
  24. */
  25. protected abstract void printList(String prefix);
  26. // 显示代码类文字
  27. public String toString() {
  28. return getName() + " (" + getSize() +")";
  29. }
  30. }

子类 File

  1. package Composite;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/7
  5. *
  6. * @example
  7. * new File("readne.md", 1000);
  8. **/
  9. public class File extends Entry {
  10. private String name;
  11. private int size;
  12. public File(String name, Integer size) {
  13. this.name = name;
  14. this.size = size;
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getSize() {
  23. return size;
  24. }
  25. public void setSize(int size) {
  26. this.size = size;
  27. }
  28. // 使用 this,自动调用 toString() 等价于 this.toString()
  29. protected void printList(String prefix) {
  30. System.out.println(prefix + "/" + this);
  31. }
  32. }

Directory 目录类

  1. package Composite;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. /**
  5. * @Author Gorit
  6. * @Date 2021/10/7
  7. * @desc 表示文件夹的类
  8. **/
  9. public class Dirextory extends Entry {
  10. private String name; // 文件夹名称
  11. private ArrayList directory = new ArrayList(); // 文件夹中目录的集合
  12. public Dirextory(String name) {
  13. this.name = name;
  14. }
  15. public String getName() { // 获取名字
  16. return name;
  17. }
  18. // 这里我们无法确认是目录的实例 还是 文件的实例,所有用 Iterator,都可以得到大小
  19. public int getSize() {
  20. int size = 0;
  21. Iterator it = directory.iterator();
  22. while (it.hasNext()) {
  23. Entry entry = (Entry)it.next();
  24. size += entry.getSize();
  25. }
  26. return size;
  27. }
  28. public Entry add(Entry entry) {
  29. directory.add(entry);
  30. return this;
  31. }
  32. protected void printList(String prefix) { // 显示目录条目一览
  33. System.out.println(prefix + "/" + this);
  34. Iterator it = directory.iterator();
  35. while (it.hasNext()) {
  36. Entry entry = (Entry)it.next();
  37. entry.printList(prefix + "/" + name);
  38. }
  39. }
  40. public void setName(String name) {
  41. this.name = name;
  42. }
  43. public ArrayList getDirectory() {
  44. return directory;
  45. }
  46. public void setDirectory(ArrayList directory) {
  47. this.directory = directory;
  48. }
  49. }

FileTrementException

  1. package Composite;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/7
  5. **/
  6. public class FileTreatmentException extends RuntimeException {
  7. public FileTreatmentException() {
  8. }
  9. public FileTreatmentException(String msg) {
  10. super(msg);
  11. }
  12. }

Main

  1. package Composite;
  2. /**
  3. * @Author Gorit
  4. * @Date 2021/10/7
  5. * 使用 main 创建如下目录树
  6. * - root
  7. * - bin
  8. * - vi
  9. * - latex
  10. * - tmp
  11. * - usr
  12. * - yuki
  13. * - diary.html
  14. * - Composite.java
  15. * - hanko
  16. * - memo.tax
  17. * - tomura
  18. * - game.doc
  19. * - junk.mail
  20. **/
  21. public class Main {
  22. public static void main(String[] args) {
  23. try {
  24. System.out.println("Making root entries...");
  25. Dirextory rootDir = new Dirextory("root");
  26. Dirextory bindDir = new Dirextory("bin");
  27. Dirextory tmpDir = new Dirextory("tmp");
  28. Dirextory usrDir = new Dirextory("usr");
  29. rootDir.add(bindDir);
  30. rootDir.add(tmpDir);
  31. rootDir.add(usrDir);
  32. bindDir.add(new File("vi", 10000));
  33. bindDir.add(new File("latex", 20000));
  34. rootDir.printList();
  35. System.out.println("");
  36. System.out.println("Making user entries...");
  37. Dirextory yuki = new Dirextory("yuki");
  38. Dirextory hanko = new Dirextory("hanko");
  39. Dirextory tomura = new Dirextory("tomura");
  40. usrDir.add(yuki);
  41. usrDir.add(hanko);
  42. usrDir.add(tomura);
  43. yuki.add(new File("diary.html", 1001));
  44. yuki.add(new File("Composite.java", 200));
  45. hanko.add(new File("memo.tak", 300));
  46. tomura.add(new File("game.doc", 400));
  47. tomura.add(new File("junk.mail", 500));
  48. rootDir.printList();
  49. } catch (FileTreatmentException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }

image.png

1.3 Composite 模式中登场的角色

1.3.1 Leaf(树叶)

表示“内容”的角色。在该橘色中不能放入其他对象。在示例程序中,由 File 类扮演此角色

1.3.2 Composite(复合物)

表示容器的角色。可以在其中放入 Leaf 角色和 Composite 角色。在示例程序中,用 Directory 类扮演此角色

1.3.3 Component

使 Leaf 角色和 Composite 角色具有一致性的角色。Component 角色是Leaf 角色 和 Composte 角色的父类。在此示例程序中,由 Entry 类扮演次橘色

1.3.4 Client

使用 Composite 的角色,在示例程序中,由 Main 类扮演此角色

1.4 拓展思路

1.4.1 多个和单个的一致性

使用 Composite 模式可以使容器 和 内容具有一致性,也可以称其为 多个单个一致性

1.4.2 到处存在递归结构

  1. 视窗系统(一个窗口可以含有一个子窗口)
  2. 文章列表,各列表可以互相嵌套
  3. 树结构的数据结构都适用于 Composite 模式。