“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

In a computer file system, a folder is a special file that can go into files or into other folders, subfolders. You can also continue to nest folders within subfolders, which form a tree structure. In software development, we usually use the composite pattern to manage this whole-part structure.

Portfolio model

Composite pattern: The Composite pattern is also called the part-whole pattern and belongs to the structural pattern. It can combine objects into a tree-like hierarchy, keeping containers (branches) and contents (leaves) consistent and easy for developers to manage and call.

The composite pattern is often used in scenarios where tree data is manipulated. We call the top node of the tree root node, which can contain branch node and leaf node, and support cyclic nesting, that is, branch node can contain branch node and leaf node. (For example, the file system of an operating system is a nested recursive tree structure that can be implemented using a composite pattern.)

Composition pattern structure

The composite pattern is implemented by the following structure:

  • Leaf: A class that represents content. No other objects can be placed in the Leaf. For example, files in the operating system cannot be placed into folders.
  • Composite: An object that represents a container in Composite mode. Leaves and branches can be placed under the branches. For example, folders in the operating system can be placed in both files and subfolders.
  • Abstract Component: Objects that make containers and contents consistent and define common interfaces for leaves and branches.

Actual Application Scenarios

  • File system: Whether in the operating system file system or other file systems such as web disk, the use of recursive nested tree structure is extensive. This is especially suitable for file management using composite mode.
  • Customization of decision tree: For example, the recommendation system needs to make decision recommendation based on multiple dimensions of population, gender, age and label, so the combination mode can be used to generate decision tree for accurate recommendation without using a large number ofif elseStatement to determine which branch of the selection is needed.

Two forms of the combinatorial pattern

The combination mode is divided into the following two forms:

  • Transparent composition mode
  • Security combination mode

The only difference between these two forms of implementation is which class manages the methods that provide the operations. Tree operations usually include add() remove() getChildren() and so on. In transparent composition mode, operations are managed directly by components. In secure Composite mode, the operations are managed by the Composite.

Below we’ll look at practical examples of why these two different patterns are needed and what the two implementations look like.

The sample

Let’s simulate the operating system file system structure, to achieve a simple file system. This file system can add folders and files, and folders can contain n subfolders. Do it in composite mode.

Transparent mode

First, we create an abstract Component:

TreeComponent.java

package com.yeliheng.composite;

import java.util.List;

/** * Abstract component: used to keep the consistency of branches and leaves */
public interface TreeComponent {

    TreeComponent add(TreeComponent node);

    List<TreeComponent> getChildren(a);

    void remove(TreeComponent node);

}
Copy the code

In this abstract component, we provide three methods: add() to add, getChildren() to get all the children, and remove() to remove. The folders and files to be created will implement the methods provided in the abstract component. Here, you might notice a problem. What happens if you continue to add folders or files to the file? Yes, this is the bottleneck of the transparent model. The transparent pattern defines methods directly in abstract components, causing all objects that implement it to have to implement the methods it provides. Under the rules we define, no subfolders or files are allowed in a file, so the method must be implemented with an exception.

Next, we create the folder object. The folder object belongs to our branch node. It can have an infinite number of children below it.

Directory.java

package com.yeliheng.composite;

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

/** * Branch node: folder */
public class Directory implements TreeComponent{
    // The folder name
    private String name;
    / / list
    private List<TreeComponent> list = new ArrayList<>();

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

    @Override
    public TreeComponent add(TreeComponent node) {
        list.add(node);
        return this;
    }

    @Override
    public List<TreeComponent> getChildren(a) {
        return list;
    }

    @Override
    public void remove(TreeComponent node) { list.remove(node); }}Copy the code

We use lists to manage our tree data.

Then the implementation of the file

File.java


package com.yeliheng.composite;

import java.util.List;

/** * leaf node: file */
public class File implements TreeComponent{

    / / file name
    private String filename;

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

    @Override
    public TreeComponent add(TreeComponent node) {
        // No new file or folder can be created under the file
        throw new UnsupportedOperationException();
    }

    @Override
    public List<TreeComponent> getChildren(a) {
        return null;
    }

    @Override
    public void remove(TreeComponent node) {
        // There is nothing under the file to delete
        throw newUnsupportedOperationException(); }}Copy the code

In a file object, an attempt to call the add() or remove() methods will result in an exception being thrown.

Finally, we build the file tree in the main function and use the Debug tool of IDEA to view the tree structure we have built.

Main.java


package com.yeliheng.composite;

public class Main {

    public static void main(String[] args) {
        TreeComponent rootElement = new Directory("Folder 1");

        rootElement.add(new Directory("Subfolder 1")
                .add(new File("Document 1"))
                .add(new File("File 2"))); rootElement.add(new Directory("Subfolder 2")); System.out.println(rootElement); }}Copy the code

After creating the tree data, we set the breakpoint at System.out.println(rootElement) to see the value of the rootElement variable.

As you can see, the tree is built correctly.

An exception is thrown when we try to add a file object. As shown below:

Next, we modify the program using safe mode.

Safe mode

Implementing the safe composition pattern simply moves the methods defined in the Component object to the branch node to prevent erroneous calls to child nodes.

In this case, we move the Add () and remove() methods from the TreeComponent class to Directory, which prevents File from calling add() and remove().

The detailed code is as follows:

TreeComponent.java


package com.yeliheng.composite;

import java.util.List;

/** * Abstract component: used to keep the consistency of branches and leaves */
public interface TreeComponent {

    List<TreeComponent> getChildren(a);


}

Copy the code

Directory.java

package com.yeliheng.composite;

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

/** * Branch node: folder */
public class Directory implements TreeComponent{
    // The folder name
    private String name;
    / / list
    private List<TreeComponent> list = new ArrayList<>();

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

    public TreeComponent add(TreeComponent node) {
        list.add(node);
        return this;
    }

    @Override
    public List<TreeComponent> getChildren(a) {
        return list;
    }


    public void remove(TreeComponent node) { list.remove(node); }}Copy the code

File.java

package com.yeliheng.composite;

import java.util.List;

/** * leaf node: file */
public class File implements TreeComponent{

    / / file name
    private String filename;

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

    @Override
    public List<TreeComponent> getChildren(a) {
        return null; }}Copy the code

In this way, the safe combination pattern is realized.

The pros and cons of the composite model

advantages

  • Using the composite pattern enables the caller to work consistently with containers and objects, regardless of the distinction between “branch nodes” and “leaf nodes.”
  • Meet the open and close principle, you can operate the new object without modifying the source code.

disadvantages

  • Overuse will increase software hierarchy and system complexity.

conclusion

Taking the file system of operating system as an example, this paper explains two realization forms of combination mode: transparent combination mode and secure combination mode.

See Github for the full source code for this article’s examples