3.3. Composite

Maheshmaddi
4 min readApr 9, 2023

--

The Composite pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It enables clients to treat individual objects and compositions of objects uniformly. The pattern is particularly useful when you need to work with a hierarchy of objects that have a similar interface or share some functionality.

The Composite pattern is typically used when:

  1. You want to represent a part-whole hierarchy of objects.
  2. You want clients to be able to treat individual objects and compositions of objects uniformly.

To implement the Composite pattern, follow these steps:

  1. Define a common interface or abstract class for both the individual objects (leaf nodes) and the composite objects (non-leaf nodes) in the hierarchy.
  2. Create concrete classes for the leaf nodes that implement the common interface or extend the abstract class.
  3. Create a composite class for the non-leaf nodes that also implements the common interface or extends the abstract class. This class should contain a collection of child components, as well as methods for adding, removing, and accessing them.
  4. Implement the common methods in the leaf and composite classes, delegating calls to the child components as needed.

Here’s a simple example of the Composite pattern in Java:

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

// Component interface
public interface Component {
void operation();
}

// Leaf class
public class Leaf implements Component {
@Override
public void operation() {
System.out.println("Leaf operation");
}
}

// Composite class
public class Composite implements Component {
private List<Component> children = new ArrayList<>();

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

public void remove(Component component) {
children.remove(component);
}

@Override
public void operation() {
System.out.println("Composite operation");
for (Component child : children) {
child.operation();
}
}
}

// Client code
public class Client {
public static void main(String[] args) {
Composite composite = new Composite();
composite.add(new Leaf());
composite.add(new Leaf());

Composite subComposite = new Composite();
subComposite.add(new Leaf());
composite.add(subComposite);

composite.operation(); // Outputs: "Composite operation", "Leaf operation", "Leaf operation", "Composite operation", "Leaf operation"
}
}

In this example, the Component interface represents the common interface for both leaf and composite objects. The Leaf class implements the Component interface, while the Composite class also implements the Component interface and manages a collection of child components. The operation() method in the Composite class delegates the call to its child components.

Advantages of the Composite pattern:

  1. Simplified client code: The Composite pattern allows clients to treat individual objects and compositions of objects uniformly, simplifying the client code.
  2. Easier manipulation of the object structure: The pattern makes it easy to add, remove, or modify components in the hierarchy without affecting the client code.

Disadvantages of the Composite pattern:

  1. Increased complexity: The Composite pattern introduces additional classes and interfaces, which can increase the overall complexity of the code.
  2. Potential for overly generalized designs: The pattern may lead to designs where the components are too generalized, making it difficult to enforce specific constraints or validate the object structure.

When using the Composite pattern, consider its benefits and drawbacks carefully. Use the pattern when you need to represent a part-whole hierarchy of objects and want clients to treat individual objects and compositions of objects uniformly. Be aware of the potential complexity and risk of overly generalized designs that may be introduced by the pattern.

Use Case: File System Navigation

Class diagram for File System Navigation using Composite pattern

Suppose you are building a file system navigation application that can traverse directories and files. You want to implement a hierarchy of objects that represent files and directories so that you can traverse them in a uniform way. The Composite pattern can be used to achieve this.

Here’s how you could implement the File and Directory classes using the Composite pattern in Java:

import java.util.*;

// Component interface
interface FileSystem {
void ls();
}

// Leaf class
class File implements FileSystem {
private String name;

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

public void ls() {
System.out.println(name);
}
}

// Composite class
class Directory implements FileSystem {
private String name;
private List<FileSystem> children;

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

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

public void remove(FileSystem component) {
children.remove(component);
}

public void ls() {
System.out.println(name);
for (FileSystem component : children) {
component.ls();
}
}
}

// Client code
public class FileSystemNavigation {
public static void main(String[] args) {
Directory root = new Directory("root");
Directory usr = new Directory("usr");
Directory bin = new Directory("bin");
Directory etc = new Directory("etc");
File bashrc = new File(".bashrc");
File profile = new File(".profile");
File ls = new File("ls");

root.add(usr);
root.add(bashrc);
root.add(profile);

usr.add(bin);
usr.add(etc);

bin.add(ls);

root.ls();
}
}

In this example, the FileSystem interface is the Component interface that defines the operations that both File and Directory classes can perform. The File class is a Leaf class that represents a file, and the Directory class is a Composite class that represents a directory. The Directory class has a List of children, which can be either File or Directory objects.

The main() method in the FileSystemNavigation class creates a hierarchy of objects by adding File and Directory objects to each other. Finally, the ls() method is called on the root Directory object to traverse the entire hierarchy and print the names of all files and directories.

Note: For complete list of design patterns click here

--

--