Easy Tutorial
❮ Programmer Joke 26 Android Tutorial Audiomanager ❯

Composite Pattern in Java and Its Application Scenarios

Category Programming Technology

The composite pattern involves containing objects within other objects, where these contained objects can be either terminal (do not contain other objects) or non-terminal (contain other objects, also known as group objects). We refer to these objects as nodes, meaning a root node contains many child nodes, some of which may not contain further child nodes, while others do, and so on. This clearly forms a tree structure, where terminal nodes are called leaf nodes, non-terminal nodes (group nodes) are called branch nodes, and the first node is the root node. It also resembles the structure of a file directory: files can be considered terminal nodes, and directories can be considered non-terminal nodes (group nodes).


Ordinary Implementation

  1. Let's first look at an ordinary implementation of a directory structure:

Directory Node: Noder

import java.util.ArrayList;
import java.util.List;

/**
 * Directory Node
 * Contains:
 *         1. Directory name
 *         2. List of sub-files
 *         3. List of sub-directories
 *         4. Method to add a file
 *         5. Method to add a directory
 *         6. Method to display sub-contents
 */
public class Noder {
    String nodeName; // Directory name
    // Constructor to name the directory
    public Noder(String nodeName) {
        this.nodeName = nodeName;
    }
    List<Noder> nodeList = new ArrayList<Noder>(); // List of sub-directories
    List<Filer> fileList = new ArrayList<Filer>(); // List of sub-files
    // Add a sub-directory
    public void addNoder(Noder noder) {
        nodeList.add(noder);
    }
    // Add a file
    public void addFiler(Filer filer) {
        fileList.add(filer);
    }
    // Display sub-directories and files
    public void display() {
        for (Noder noder : nodeList) {
            System.out.println(noder.nodeName);
            noder.display(); // Recursively display directory list
        }
        for (Filer filer : fileList) {
            filer.display();
        }
    }
}

File Node: Filer

/**
 * File Node
 * File nodes are terminal nodes, with no sub-nodes
 * Contains:
 *         1. File name
 *         2. File display method
 */
public class Filer {
    String fileName; // File name
    public Filer(String fileName) {
        this.fileName = fileName;
    }
    // File display method
    public void display() {
        System.out.println(fileName);
    }
}

Test Class: Clienter

import java.io.File;

public class Clienter {
    public static void createTree(Noder node) {
        File file = new File(node.nodeName);
        File[] f = file.listFiles();
        for (File fi : f) {
            if (fi.isFile()) {
                Filer filer = new Filer(fi.getAbsolutePath());
                node.addFiler(filer);
            }
            if (fi.isDirectory()) {
                Noder noder = new Noder(fi.getAbsolutePath());
                node.addNoder(noder);
                createTree(noder); // Use recursion to generate tree structure
            }
        }
    }
    public static void main(String[] args) {
        Noder noder = new Noder("E://ceshi");
        createTree(noder); // Create directory tree structure
        noder.display(); // Display directories and files
    }
}

Execution Result:

E:\ceshi\目录1
E:\ceshi\目录1\目录3
E:\ceshi\目录1\文件2.txt
E:\ceshi\目录2
E:\ceshi\目录2\文件3.txt
E:\ceshi\文件1.txt

2. Composite Pattern

From the above code, we can see that we have defined file node objects and directory node objects separately, because files and directories have different operations. Files do not have sub-nodes, while directories can have sub-nodes. However, we can consider that both files and directories can exist as sub-nodes of a node. Although their operations are different, we can define them in the method implementation of the classes, such as throwing an exception in the file's method for adding sub-nodes, without specific implementation, while directories implement the addition operation specifically. Both have display operations and can be implemented individually. Since we have abstracted files and directories into a single type, we can use polymorphism to implement as follows:

Abstract Class: Node

/**
 * Treat files and directories as a type of node, define an abstract class for this node, and use its implementation classes to distinguish files and directories, defining their specific implementations in these classes
 */
public abstract class Node {
    protected String name; // Name
    // Constructor to assign a name
    public Node(String name) {
        this.name = name;
    }
    // Add node: Files do not have this method, directories override this method
    public void addNode(Node node) throws Exception {
        throw new Exception("Invalid exception");
    }
    // Display node: Both files and directories implement this method
    abstract void display();
}

File Implementation Class: Filer

/**
 * Implement file node
 */
public class Filer extends Node {
    // Constructor to name the file node
    public Filer(String name) {
        super(name);
    }
    // Display file node
    @Override
    public void display() {
        System.out.println(name);
    }
}

Directory Implementation Class: Noder

import java.util.*;

/**
 * Implement directory node
 */
public class Noder extends Node {
    List<Node> nodeList = new ArrayList<Node>(); // Internal node list (including files and sub-directories)
    // Constructor to name the current directory node
    public Noder(String name) {
        super(name);
    }
    // Add node
    public void addNode(Node node) throws Exception {
        nodeList.add(node);
    }
    // Recursively display sub-nodes
    @Override
    void display() {
        System.out.println(name);
        for (Node node : nodeList) {
            node.display();
        }
    }
}

Test Class: Clienter

import java.io.File;

public class Clienter {
    public static void createTree(Node node) throws Exception {
        File file = new File(node.name);
        File[] f = file.listFiles();
        for (File fi : f) {
            if (fi.isFile()) {
                Filer filer = new Filer(fi.getAbsolutePath());
                node.addNode(filer);
            }
            if (fi.isDirectory()) {
                Noder noder = new Noder(fi.getAbsolutePath());
                node.addNode(noder);
                createTree(noder); // Use recursion to generate tree structure
            }
        }
    }
    public static void main(String[] args) {
        Node noder = new Noder("E://ceshi");
        try {
            createTree(noder);
        } catch (Exception e) {
            e.printStackTrace();
        }
        noder.display();
    }
}

Execution Output Result:

E://ceshi
E:\ceshi\文件1.txt
E:\ceshi\目录1
E:\ceshi\目录1\文件2.txt
E:\ceshi\目录1\目录3
E:\ceshi\目录2
E:\ceshi\目录2\文件3.txt

From the above implementation, it can be seen that the composite pattern involves objects containing other objects, achieved through composition (referencing objects within objects). I believe this composition is distinct from inheritance. Another layer of meaning refers to the abstraction of child nodes in a tree structure (abstracting leaf nodes and branch nodes as child nodes), which differs from the conventional approach of defining leaf nodes and branch nodes separately.


3. Application Scenarios of Composite Pattern

This composite pattern is designed for tree structures, so its application scenarios are where tree structures appear. For example: displaying file directories, presenting multi-level directories, and other tree-structured data operations.

Original Source: https://www.cnblogs.com/V1haoge/p/6489827.html

❮ Programmer Joke 26 Android Tutorial Audiomanager ❯