一天一种JAVA设计模式之十一:组合模式

430 阅读5分钟

写在前面的话

复习、总结23种设计模式

获取详细源码请点击我

上一篇

# 一天一种JAVA设计模式之十:桥接模式

组合模式

记重点

只要是树形结构,就可以考虑使用组合模式(如树形菜单,文件、文件夹的管理)

  1. 维护和展示部分-整体关系的场景, 如树形菜单、 文件和文件夹管理。
  2. 从一个整体中能够独立出部分模块或功能的场景

什么是组合模式(合成模式、部分-整体模式)

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

image.png

  • Component 抽象构件角色

定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。

  • Leaf 叶子节点

叶子对象,其下再也没有其他的子节点,是遍历的最小单位。

  • Composite 树枝构件

树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构。

组合模式通用代码实现

Component抽象构件角色,定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性

package com.design.pattern.composite.test01;  
  
// Component 抽象构件角色  
//定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性  
public abstract class Component {  
    // 个体和整体都有的共享方法     
    public void doSth() {  

    }  
}

Composite树枝构件,树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构

package com.design.pattern.composite.test01;  
  
import java.util.ArrayList;  
import java.util.List;  

// 组合模式的重点
// Composite 树枝构件  
//树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构  
public class Composite extends Component {  
  
    private List<Component> componentList = new ArrayList<>();  
    // 可以增加一个叶子节点或者树枝构件
    public void add(Component component) {  
        componentList.add(component);  
    }  

    public void remove(Component component) {  
        componentList.remove(component);  
    }  

    public List<Component> getChildren() {  
        return componentList;  
    }  
}

Leaf叶子节点,叶子对象,其下再也没有其他的子节点,是遍历的最小单位

package com.design.pattern.composite.test01;  
  
// Leaf叶子节点  
// 叶子对象,其下再也没有其他的子节点,是遍历的最小单位  
public class Leaf extends Component {  
  
    @Override  
    public void doSth() {  
        System.out.println("叶子节点逻辑");  
    }  
}

测试类

package com.design.pattern.composite.test01;  
  
public class Client {  
  
    public static void main(String[] args) {  
        // 创建一个根节点  
        Composite root = new Composite();  
        root.doSth();  
        // 创建一个树枝节点  
        Composite branch = new Composite();  
        // 创建一个叶子节点  
        Composite leaf = new Composite();  

        // 串联起来  
        root.add(branch);  
        branch.add(leaf);  

        // 遍历  
        display(root);  
    }  

    private static void display(Composite root) {  
        for (Component child : root.getChildren()) {  
            if (child instanceof Leaf) {  
                child.doSth();  
            } else {  
                display((Composite) child);  
            }  
        }  
    }  
}

以一个经典笔试题为例请打印显示文件夹结构的功能, 按如下格式显示

  • 我是根目录

    • 我是二级目录

      • 我是文件

抽象构件(Component):文件夹和文件统一为文件

package com.design.pattern.composite.test02;  
  
public abstract class FileComponent {  
    //文件名称  
    protected String name;  
    //文件的层级 1 一级目录 2 二级目录 ...  
    protected Integer level;  
    //文件的类型 1 文件夹 2文件  
    protected Integer type;  

    //添加子文件/文件夹  
    public abstract void add(FileComponent fileComponent);  

    //移除子文件/文件夹  
    public abstract void remove(FileComponent fileComponent);  

    //获取指定的子文件/文件夹  
    public abstract FileComponent getChild(int index);  

    //打印子 子文件/子文件夹 名称的方法  
    public abstract void print();  
}

树枝构件(Composite):文件夹

package com.design.pattern.composite.test02;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class FileFolder extends FileComponent {  
    //文件夹可以有多个子文件夹或者子文件  
    private List<FileComponent> fileComponentList;  

    public FileFolder(String name, Integer level, Integer type) {  
        this.name = name;  
        this.level = level;  
        this.type = type;  
        this.fileComponentList = new ArrayList<>();  
    }  

    @Override  
    public void add(FileComponent fileComponent) {  
        fileComponentList.add(fileComponent);  
    }  

    @Override  
    public void remove(FileComponent fileComponent) {  
        fileComponentList.remove(fileComponent);  
    }  

    @Override  
    public FileComponent getChild(int index) {  
        return fileComponentList.get(index);  
    }  

    @Override  
    public void print() {  
        //打印菜单名称  
        for (int i = 0; i < level; i++) {  
            System.out.print("\t");  
        }  
        System.out.println(name);  
        //打印子菜单或者子菜单项名称  
        for (FileComponent component : fileComponentList) {  
            component.print();  
        }  
    }  
}

树叶构件(Leaf):文件

package com.design.pattern.composite.test02;  
  
public class FileItem extends FileComponent {  
    public FileItem(String name, Integer level, Integer type) {  
        this.name = name;  
        this.level = level;  
        this.type = type;  
    }  

    @Override  
    public void add(FileComponent fileComponent) {  

    }  

    @Override  
    public void remove(FileComponent fileComponent) {  

    }  

    @Override  
    public FileComponent getChild(int index) {  
        return null;  
    }  

    @Override  
    public void print() {  
        //打印文件的名称  
        for (int i = 0; i < level; i++) {  
            System.out.print("\t");  
        }  
        System.out.println(name);  
    }  
}

遍历文件夹

package com.design.pattern.composite.test02;  
  
public class Test {  
    public static void main(String[] args) {  
        //定义根目录  
        FileComponent rootComponent = new FileFolder("我是根目录", 1, 1);  
        //定义二级文件夹  
        FileComponent secondLevelComponent = new FileFolder("我是二级目录", 2, 1);  
        //定义文件  
        FileComponent file = new FileItem("我是文件", 3, 2);  
        //向根目录添加二级目录  
        rootComponent.add(secondLevelComponent);  
        //向二级目录添加文件  
        secondLevelComponent.add(file);  
        //打印  
        rootComponent.print();  
    }  
}

组合模式的优点

高层模块调用简单

一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

节点自由增加

使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。

组合模式的缺点

组合模式有一个非常明显的缺点,看到我们在场景类中的定义,提到树叶和树枝使用时的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突,读者在使用的时候要考虑清楚,它限制了你接口的影响范围。

  • 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系。
  • 不容易限制容器中的构件。

下一篇

# 一天一种JAVA设计模式之十:桥接模式