【设计模式-3.7】结构型——组合模式

说明:本文介绍结构型设计模式之一的组合模式

定义

组合模式(Composite Pattern)又叫作整体-部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性,属于结构型设计模式。(引自《设计模式就该这样学》P263)

文件系统

以文件系统为例,如下,是服务器上某个文件夹的文件结构,该文件夹下既有文件夹,也有文件

如果我要构建这样一个文件夹-文件系统,代码应该是这样写的,如下:

(图片文件,ImageFile)

java 复制代码
/**
 * 图片文件
 */
public class ImageFile {

    /**
     * 图片名称
     */
    private String name;

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

    /**
     * 展示
     */
    public void show(int space) {
        for (int i = 0; i < space; i++) {
            System.out.print(" ");
        }
        System.out.println(name);
    }
}

(电影文件,MovieFile)

java 复制代码
/**
 * 电影文件
 */
public class MovieFile {

    /**
     * 电影名称
     */
    private String name;

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

    /**
     * 展示
     */
    public void show(int space) {
        for (int i = 0; i < space; i++) {
            System.out.print(" ");
        }
        System.out.println(name);
    }
}

(文件夹,Folder,定义多个文件集合,并开放对应的增加方法)

java 复制代码
import java.util.ArrayList;

/**
 * 文件夹
 */
public class Folder {
    /**
     * 文件夹名称
     */
    private String name;

    /**
     * 文件夹下的文件夹
     */
    private ArrayList<Folder> folders = new ArrayList<>();

    /**
     * 文件夹下的图片文件
     */
    private ArrayList<ImageFile> imageFiles = new ArrayList<>();

    /**
     * 文件夹下的视频文件
     */
    private ArrayList<MovieFile> movieFiles = new ArrayList<>();

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

    /**
     * 添加文件夹
     */
    public void addFolder(Folder folder) {
        folders.add(folder);
    }

    /**
     * 添加图片文件
     */
    public void addImageFile(ImageFile imageFile) {
        imageFiles.add(imageFile);
    }

    /**
     * 添加电影文件
     */
    public void addMoveFile(MovieFile movieFile) {
        movieFiles.add(movieFile);
    }

    /**
     * 展示
     */
    public void show(int space) {
        // 打印文件夹
        space++;
        for (int i = 0; i < space; i++) {
            System.out.print(" ");
        }
        System.out.println(name);
        for (Folder folder : folders) {
            folder.show(space);
        }

        // 打印图片文件
        space++;
        for (ImageFile imageFile : imageFiles) {
            imageFile.show(space);
        }

        // 打印电影文件
        space++;
        for (MovieFile movieFile : movieFiles) {
            movieFile.show(space);
        }
    }
}

(客户端使用,Client)

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 顶级文件夹
        Folder folder = new Folder("folder");

        // 二级文件夹
        Folder images = new Folder("images");
        Folder movies = new Folder("movies");

        // 三级目录下的文件
        ImageFile boy = new ImageFile("boy.png");
        ImageFile girl = new ImageFile("girl.png");
        images.addImageFile(boy);
        images.addImageFile(girl);

        // 三级文件夹
        Folder director1 = new Folder("heizeming");
        Folder director2 = new Folder("xiaolinzhengshu");

        // 四级目录
        MovieFile movieFile1 = new MovieFile("luoshengmen.mp4");
        MovieFile movieFile2 = new MovieFile("qiwushi.mp4");
        director1.addMoveFile(movieFile1);
        director1.addMoveFile(movieFile2);

        MovieFile movieFile3 = new MovieFile("duomingjian.mp4");
        MovieFile movieFile4 = new MovieFile("qiefu.mp4");
        director2.addMoveFile(movieFile3);
        director2.addMoveFile(movieFile4);
        movies.addFolder(director1);
        movies.addFolder(director2);

        // 文件夹添加到顶级文件夹
        folder.addFolder(images);
        folder.addFolder(movies);
        folder.show(0);
    }
}

运行,hora!(看!),能实现目的

但这里存在问题,文件系统,简单来说只有文件夹和文件两个实体对象,代码中的图片文件、电影文件可以抽象为文件(File),这是整体与个体的场景。

组合模式

使用组合模式改进上述代码,如下:

(抽象节点类,Node)

java 复制代码
/**
 * 抽象节点
 */
public abstract class Node {

    /**
     * 节点名称
     */
    protected String name;

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

    /**
     * 添加节点
     */
    protected abstract void add(Node node);

    /**
     * 展示
     */
    protected void show(int space) {
        for (int i = 0; i < space; i++) {
            System.out.print(" ");
        }
        System.out.println(name);
    }

    /**
     * 重载方法,使用的时候就不用给参数了
     */
    protected void show() {
        show(0);
    }
}

(文件夹,Folder)

java 复制代码
import java.util.ArrayList;

/**
 * 文件夹
 */
public class Folder extends Node {

    /**
     * 文件夹下的子节点
     */
    private ArrayList<Node> childrenNodes = new ArrayList<>();

    /**
     * 调用父类的构造方法
     */
    public Folder(String name) {
        super(name);
    }

    @Override
    protected void add(Node node) {
        childrenNodes.add(node);
    }

    @Override
    protected void show(int space) {
        super.show(space);
        space++;
        for (Node node : childrenNodes) {
            node.show(space);
        }
    }
}

(文件,File)

java 复制代码
/**
 * 文件
 */
public class File extends Node {

    /**
     * 调用父类的构造方法
     */
    public File(String name) {
        super(name);
    }

    @Override
    protected void add(Node node) {
        System.out.println("不能添加子节点");
    }

    @Override
    protected void show(int space) {
        super.show(space);
    }
}

(客户端使用,Client)

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 顶级文件夹
        Folder folder = new Folder("folder");

        // 二级文件夹
        Folder images = new Folder("images");
        Folder movies = new Folder("movies");

        // 三级目录下的文件
        File boy = new File("boy.png");
        File girl = new File("girl.png");
        images.add(boy);
        images.add(girl);

        // 三级文件夹
        Folder director1 = new Folder("heizeming");
        Folder director2 = new Folder("xiaolinzhengshu");

        // 四级目录
        File movieFile1 = new File("luoshengmen.mp4");
        File movieFile2 = new File("qiwushi.mp4");
        director1.add(movieFile1);
        director1.add(movieFile2);

        File movieFile3 = new File("duomingjian.mp4");
        File movieFile4 = new File("qiefu.mp4");
        director2.add(movieFile3);
        director2.add(movieFile4);
        movies.add(director1);
        movies.add(director2);

        // 文件夹添加到顶级文件夹
        folder.add(images);
        folder.add(movies);
        folder.show();
    }
}

执行如下,也实现了目的

这么看下来,文件系统场景使用组合模式实现是很不错的,代码少了很多,也削减了文件夹类中的职责(可以对比下Folder类前后的代码)

使用场景

在《设计模式就该这样学》(P229)这本书中,提到状态模式适用于以下场景:

(1)希望客户端可以忽略组合对象与单个对象的差异;

(2)对象层次具备整体和部分,呈树形结构;

除了文件系统、企业组织架构场景,我还没想到其他使用场景;

总结

本文介绍了结构型设计模式中的组合模式,参考《设计模式就该这样学》、《秒懂设计模式》、《设计模式的艺术》(第一版)这三本书,其中的例子来自《秒懂设计模式》。

相关推荐
维天说4 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19945 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
杨充6 小时前
11.DDD与战术建模
设计模式·开源·代码规范
杨充6 小时前
12.综合实战图片框架
设计模式·开源·代码规范
噢,我明白了6 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品6 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘6 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记7 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
糖果店的幽灵7 小时前
langgraph的 MessagesState 解读
java·开发语言·人工智能·windows·langgraph
极光代码工作室8 小时前
基于SpringBoot的在线博客系统
java·springboot·web开发·后端开发