1 组合模式介绍

组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示部分-整体的层次结构。组合模式使得客户端可以统一处理单个对象和组合对象,从而简化了客户端代码。

在组合模式中,有两种类型的对象:叶子节点和组合节点。叶子节点表示树形结构中的最底层节点,它们不能再分解为更小的部分。组合节点表示树形结构中的中间节点和根节点,它们可以包含一个或多个子节点,这些子节点可以是叶子节点,也可以是其他的组合节点。

组合模式的核心思想是通过递归方式来遍历整个树形结构,从而实现对整体和部分的操作。客户端可以通过操作组合节点来操作整个树形结构,也可以通过操作叶子节点来操作单个对象。

📌 场景

  • 树形结构:当需要处理一个树形结构的对象集合时,可以使用组合模式。例如操作系统的文件系统,文件夹和文件的嵌套结构。

  • 命令模式:当需要执行一系列操作时,可以使用组合模式将这些操作组合成一个命令对象。例如文本编辑器中的撤销和重做功能。

  • 菜单和导航:当需要创建菜单和导航时,可以使用组合模式。例如网站的导航栏和下拉菜单。

  • 组合对象的属性:当需要对组合对象进行属性设置时,可以使用组合模式。例如在游戏中设计角色的属性,可以将角色的各个部分组合起来形成一个复合对象。

  • 组合对象的操作:当需要对组合对象进行操作时,可以使用组合模式。例如在图形编辑器中,可以对多个图形对象进行组合操作,包括平移、旋转、缩放等。

📌 优缺点

  • 优点:简化客户端代码、统一处理单个对象和组合对象、方便增加新的节点类型、提高代码复用性和可维护性等;

  • 缺点:可能会导致设计过度复杂,不适合处理复杂的逻辑。

2 组合模式实现

以目录和文件为例:

📌 1.定义组件

/**
 * 组件
 */
public abstract class Component {
    /**
     * 添加子组件
     * @param component 组件
     */
    public abstract void add(Component component);
    /**
     * 删除子组件
     * @param component 组件
     */
    public abstract void remove(Component component);
    /**
     * 获取子组件
     * @param index 组件序号
     * @return 子组件
     */
    public abstract Component getChild(int index);
    /**
     * 修改名称
     */
    public abstract void editName();
}

📌 2.定义目录

/**
 * 目录
 * <p>目录可以包含多个文件或目录
 */
public class Directory extends Component{
    /**
     * 使用 List 来存放目录中的子组件
     */
    List<Component> child = new ArrayList<>();

    @Override
    public void add(Component component) {
        child.add(component);
    }

    @Override
    public void remove(Component component) {
        child.remove(component);
    }

    @Override
    public Component getChild(int index) {
        return child.get(index);
    }

    @Override
    public void editName() {
        // 继续调用所有子组件的 editName 方法执行业务
        child.forEach(Component::editName);
    }
}

📌 3.定义文件

/**
 * 文件
 */
public class File extends Component{

    @Override
    public void add(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void remove(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void editName() {
        // 具体的名称修改操作
        System.out.println("修改文件:" + this);
    }
}

📌 4.调用

// 新建一个外层目录
Directory out = new Directory();
// 新建一个内层目录
Directory in = new Directory();
// 外层目录添加文件
out.add(new File());
// 里层目录添加文件
in.add(new File());
in.add(new File());
// 将内层目录放置在外层目录下
out.add(in);
// 修改文件名
out.editName();

控制台输出:

可以看到对最外层目录进行操作后,会递归向下处理当前目录和子目录中所有的文件。

更多推荐