第五章 5.6 组合模式

分类:01_设计模式

标签:

我们很容易将“组合模式”和“组合关系”搞混。组合模式最初只是用于解决树形结构的场景,更多的是处理对象组织结构之间的问题。而组合关系则是通过将不同对象封装起来完成一个统一功能.

5.6.1 组合模式介绍

组合模式(Composite Pattern) 的定义是:将对象组合成树形结构以表示整个部分的层次结构.组合模式可以让用户统一对待单个对象和对象的组合.

比如: windows操作系统中的目录结构,其实就是树形目录结构,通过tree命令实现树形结构展示.

94.jpg

在上图中包含了文件夹和文件两类不同元素,其中在文件夹中可以包含文件,还可以继续包含子文件夹.子文件夹中可以放入文件,也可以放入子文件夹. 文件夹形成了一种容器结构(树形结构),递归结构.

95.jpg

接着我们再来思考虽然文件夹和文件是不同类型的对象,但是他们有一个共性,就是 都可以被放入文件夹中. 其实文件和文件夹可以被当做是同一种对象看待.

组合模式其实就是将一组对象(文件夹和文件)组织成树形结构,以表示一种'部分-整体' 的层次结构,(目录与子目录的嵌套结构). 组合模式让客户端可以统一单个对象(文件)和组合对象(文件夹)的处理逻辑(递归遍历).

组合模式更像是一种数据结构和算法的抽象,其中数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现.

5.6.2 组合模式原理

96.jpg

组合模式主要包含三种角色:

5.6.3 组合模式实现

组合模式的关键在于定义一个抽象根节点类,它既可以代表叶子,又可以代表树枝节点,客户端就是针对该抽象类进行编程,不需要知道它到底表示的是叶子还是容器,可以对其进行统一处理.

树枝节点对象和抽象根节点类之间建立了一个聚合关联关系,在树枝节点对象中既可以包含叶子节点,还可以继续包含树枝节点,以此实现递归组合,形成一个树形结构.

代码实现


/**
* 抽象根节点
*      对于客户端而言将针对抽象编程,无需关心其具体子类是容器构件还是叶子构件.
* @author spikeCong
* @date 2022/10/6
**/
public abstract class Component {

   public abstract void add(Component c); //增加成员
   public abstract void remove(Component c); //删除成员
   public abstract Component getChild(int i); //获取成员
   public abstract void operation(); //业务方法

}

/**
* 叶子节点
*      叶子节点中不能包含子节点
* @author spikeCong
* @date 2022/10/6
**/
public class Leaf extends Component {
   @Override
   public void add(Component c) {
       //具体操作
   }

   @Override
   public void remove(Component c) {
       //具体操作
   }

   @Override
   public Component getChild(int i) {
       //具体操作
       return new Leaf();
   }

   @Override
   public void operation() {
       //叶子节点具体业务方法
   }
}

/**
* 树枝节点
*      容器对象,可以包含子节点
* @author spikeCong
* @date 2022/10/6
**/
public class Composite extends Component {

   private ArrayList<Component> list = new ArrayList<>();

   @Override
   public void add(Component c) {
       list.add(c);
   }

   @Override
   public void remove(Component c) {
       list.remove(c);
   }

   @Override
   public Component getChild(int i) {
       return (Component) list.get(i);
   }

   @Override
   public void operation() {
       //在树枝节点中的业务方法,将递归调用其他节点中的operation() 方法
       for (Component component : list) {
           component.operation();
       }
   }
}

5.6.4 组合模式应用实例

下面我们通过一段程序来演示一下组合模式的使用. 程序的功能是列出某一目录下所有的文件和文件夹.类图如下:

98.jpg

我们按照下图的表示,进行文件和文件夹的构建.

97.jpg

Entry类: 抽象类,用来定义File类和Directory类的共性内容


/**
* Entry抽象类,表示目录条目(文件+文件夹)的抽象类
* @author spikeCong
* @date 2022/10/6
**/
public abstract class Entry {

   public abstract String getName(); //获取文件名

   public abstract int getSize(); //获取文件大小

   //添加文件夹或文件
   public abstract Entry add(Entry entry);

   //显示指定目录下的所有信息
   public abstract void printList(String prefix);

   @Override
   public String toString() {
       return getName() + "(" +getSize() + ")";
   }
}

File类,叶子节点,表示文件.


/**
* File类 表示文件
* @author spikeCong
* @date 2022/10/6
**/
public class File extends Entry {

   private String name; //文件名
   private int size; //文件大小

   public File(String name, int size) {
       this.name = name;
       this.size = size;
   }

   @Override
   public String getName() {
       return name;
   }

   @Override
   public int getSize() {
       return size;
   }

   @Override
   public Entry add(Entry entry) {
       return null;
   }

   @Override
   public void printList(String prefix) {

       System.out.println(prefix + "/" + this);
   }

}

Directory类,树枝节点,表示文件


/**
* Directory表示文件夹
* @author spikeCong
* @date 2022/10/6
**/
public class Directory extends Entry{

   //文件的名字
   private String name;

   //文件夹与文件的集合
   private ArrayList<Entry> directory = new ArrayList();

   //构造函数
   public Directory(String name) {
       this.name = name;
   }

   //获取文件名称
   @Override
   public String getName() {
       return this.name;
   }

   /**
    * 获取文件大小
    *      1.如果entry对象是File类型,则调用getSize方法获取文件大小
    *      2.如果entry对象是Directory类型,会继续调用子文件夹的getSize方法,形成递归调用.
    */
   @Override
   public int getSize() {
       int size = 0;

       //遍历或者去文件大小
       for (Entry entry : directory) {
           size += entry.getSize();
       }
       return size;
   }

   @Override
   public Entry add(Entry entry) {
       directory.add(entry);
       return this;
   }

   //显示目录
   @Override
   public void printList(String prefix) {
       System.out.println("/" + this);
       for (Entry entry : directory) {
           entry.printList("/" + name);
       }
   }
}

测试


public class Client {

   public static void main(String[] args) {

       //根节点
       Directory rootDir = new Directory("root");

       //树枝节点
       Directory binDir = new Directory("bin");
       //向bin目录中添加叶子节点
       binDir.add(new File("vi",10000));
       binDir.add(new File("test",20000));

       Directory tmpDir = new Directory("tmp");

       Directory usrDir = new Directory("usr");
       Directory mysqlDir = new Directory("mysql");
       mysqlDir.add(new File("my.cnf",30));
       mysqlDir.add(new File("test.db",25000));
       usrDir.add(mysqlDir);

       rootDir.add(binDir);
       rootDir.add(tmpDir);
       rootDir.add(mysqlDir);

       rootDir.printList("");
   }
}


5.6.5 组合模式总结

1 ) 组合模式的分类

2 ) 组合模式优点

3 ) 组合模式的缺点

4 ) 组合模式使用场景分析


修改内容