DEV Community

Sota
Sota

Posted on

Composite Pattern

What is Composite Pattern?

Composite pattern is a structural pattern that allows you to compose objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

In Composite pattern, elements with child elements are called nodes and elements without children are called leaves.

When to use it?

  • Use Composite pattern when you need whole-part or parent-child or tree-like hierarchy.
  • Use Composite pattern when you want to let client to treat child and parent in a tree uniformly.

Problem

We are trying to implement simple file system. First of all, we need File and Directory classes. But it will be difficult for client to keep checking which instances of classes each object is as our file structure grows bigger.
File system is a perfect example of tree-like hierarchy, and we want to treat File and Directory uniformly. Time to use Composite pattern!

Solution

Image description

  1. Client
    Client doesn't care whether an object is instance of File or Directory thanks to FileSystemComponent. Additionally, client doesn't have to write if statements to make sure whether he is calling right methods on the right objects.

  2. FileSystemComponent
    File and Directory are seen as FileSystemComponent by Client. FileSystemComponent defines default behavior for methods, default behavior can be exception, doing nothing, returning null or false, whatever makes sense for your application.

  3. File
    This is our leaf, overriding print method that prints its name and content.

  4. Directory
    This is our node, overriding methods to add, remove, get children. print method prints out its name and calls children's print methods as well so that Client doesn't need to call each component's print method.

Structure

Image description

Implementation in Java

public abstract class FileSystemComponent {

    protected String name;

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

    public String getName() {
        return name;
    }

    public void print(int indentLevel) {
        throw new UnsupportedOperationException();
    }

    public void add(FileSystemComponent component) {
        throw new UnsupportedOperationException();
    }

    public void remove(int index) {
        throw new UnsupportedOperationException();
    }

    // Instead of throwing exception, we do nothing by default.
    // Doing nothing makes sense because leaf has no child.
    public void getChildren() {
    }
}
Enter fullscreen mode Exit fullscreen mode
public class File extends FileSystemComponent {

    private String content;

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

    @Override
    public void print(int indentLevel) {
        String indent = " ".repeat(indentLevel);
        System.out.println(indent + name + ": " + content);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class Directory extends FileSystemComponent {

    private List<FileSystemComponent> children;

    public Directory(String name) {
        super(name);
        children = new ArrayList<>();
    }

    @Override
    public void print(int indentLevel) {
        String indent = " ".repeat(indentLevel);
        System.out.println(indent + name + " directory:");
        for (FileSystemComponent child : children) {
            child.print(indentLevel + 2);
        }
    }

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

    @Override
    public void remove(int index) {
        children.remove(index);
    }

    @Override
    public void getChildren() {
        if (children.isEmpty()) {
            return;
        }
        StringBuilder builder = new StringBuilder("[");
        for (FileSystemComponent child : children) {
            builder.append(child.getName() + ", ");
        }
        builder.delete(builder.length() - 2, builder.length());
        builder.append("]");
        System.out.println(builder);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class FileSystemTestDrive {

    public static void main(String[] args) {
        FileSystemComponent rootDirectory = new Directory("Root");
        FileSystemComponent fruitsDirectory = new Directory("Fruits");
        FileSystemComponent animalDirectory = new Directory("Animal");
        FileSystemComponent felineDirectory = new Directory("Feline");

        rootDirectory.add(fruitsDirectory);
        rootDirectory.add(animalDirectory);

        fruitsDirectory.add(new File("Appple", "red and juicy."));
        fruitsDirectory.add(new File("Banana", "yellow and sweet."));
        fruitsDirectory.add(new File("Lemon", "yellow and sour."));

        animalDirectory.add(felineDirectory);
        felineDirectory.add(new File("Lion", "King of animal."));
        felineDirectory.add(new File("Tiger", "Has cool color pattern."));

        rootDirectory.print(0);
        rootDirectory.getChildren();
        rootDirectory.remove(0);
        rootDirectory.getChildren();

        // What happens we call getChildren() on leaf? (we don't override the method in leaf class)
        FileSystemComponent file = new File("sample", "this is leaf");
        file.getChildren(); // leaf calls default behavior, doing nothing
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Root directory:
  Fruits directory:
    Appple: red and juicy.
    Banana: yellow and sweet.
    Lemon: yellow and sour.
  Animal directory:
    Feline directory:
      Lion: King of animal.
      Tiger: Has cool color pattern.
[Fruits, Animal]
[Animal]
Enter fullscreen mode Exit fullscreen mode

Pitfalls

  • If you want a composite that keeps its children in particular order, it requires more sophisticated management scheme for adding, removing and traversing children.
  • As composite structure becomes larger and complex, it would be more expensive to traverse. In this case, you might consider to implement a cache that stores some data to save traversals.

You can check all the design pattern implementations here.
GitHub Repository

Top comments (0)