【设计模式】组合模式

发布于:2025-04-05 ⋅ 阅读:(12) ⋅ 点赞:(0)

1.模式简述
想象一个场景,有很多的文件和文件夹,文件夹中还套着很多的子文件夹。你需要统计所有文件的总大小。但是由于这些文件分布在不同的文件夹中,你无法做到一个一个的点开去统计,你想用程序的方式实现,读取所有的文件夹,然后一层一层的查询。当你尝试统计了几个文件夹后你发现还是无法快速实现。这里就需要组合模式帮你解决这个问题。当你统计文件夹中文件的大小时,无需关心文件夹中是否嵌套了子文件夹。直接调用统一的方法。系统将自动递归的计算所有的文件的大小,这就是组合模式的核心:统一处理个体和组合对象。
适用场景
需要使用树结构标识的菜单或者组织架构。

客户端统一处理单个对象和组合对象。

优点
代码简洁,不需要区分对象类型。

新增组件类型容易,符合开闭原则。

缺点
设计过于抽象,会增加开发人员理解成本。

叶子节点需要实现不需要的方法,需要使用异常捕获。

2.示例代码
类图

在这里插入图片描述

代码

// 组件抽象(可以是接口或抽象类)
interface FileSystemComponent {
void showDetails();
}


// 叶子节点:文件
class File implements FileSystemComponent {
private String name;


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


@Override
public void showDetails() {
System.out.println("File: " + name);
}
}


// 组合节点:文件夹
class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> children = new ArrayList<>();


public Directory(String name) { this.name = name; }


public void add(FileSystemComponent component) {
children.add(component);
}


@Override
public void showDetails() {
System.out.println("Directory: " + name);
for (FileSystemComponent child : children) {
child.showDetails(); // 递归调用子节点的方法
}
}
}


// 客户端调用
public class Client {
public static void main(String[] args) {
File file1 = new File("a.txt");
File file2 = new File("b.jpg");


Directory dir1 = new Directory("Documents");
dir1.add(file1);
dir1.add(file2);


Directory root = new Directory("C:");
root.add(dir1);
root.showDetails(); // 统一调用
}
}

3.实际案例
设计一个多级菜单系统,菜单可以包含子菜单或菜单项。
统一接口:所有对象(组合/叶子)实现同一接口。

递归操作:组合对象内部通过递归处理子节点。

透明性:客户端无需区分组合对象和叶子对象。

代码实现

// 组件接口
interface MenuComponent {
    void display();
}


// 叶子节点:菜单项
class MenuItem implements MenuComponent {
    private String name;


    public MenuItem(String name) {
        this.name = name;
    }


    @Override
    public void display() {
        System.out.println("菜单项: " + name);
    }
}


// 组合节点:子菜单
class SubMenu implements MenuComponent {
    private String name;
    private List<MenuComponent> children = new ArrayList<>();


    public SubMenu(String name) {
        this.name = name;
    }


    public void add(MenuComponent component) {
        children.add(component);
    }


    @Override
    public void display() {
        System.out.println("子菜单: " + name);
        for (MenuComponent child : children) {
            child.display(); // 递归显示子菜单或菜单项
        }
    }
}


// 客户端使用
public class MenuClient {
    public static void main(String[] args) {
        MenuComponent mainMenu = new SubMenu("主菜单");


        MenuComponent fileMenu = new SubMenu("文件");
        fileMenu.add(new MenuItem("新建"));
        fileMenu.add(new MenuItem("打开"));


        MenuComponent editMenu = new SubMenu("编辑");
        editMenu.add(new MenuItem("复制"));
        editMenu.add(new MenuItem("粘贴"));


        mainMenu.add(fileMenu);
        mainMenu.add(editMenu);


        mainMenu.display(); // 统一调用显示方法
    }
}