# CompositeA intermediate-level guide to Composite: before-and-after java code and diagrams for a CS student.

## Navigating Hierarchies of Parts and Wholes

Imagine you are building a file explorer. You have individual files, which represent the "leaves" or end-points of your data, and folders, which act as containers that hold both files and other folders. When you want to calculate the total size of a directory, you cannot simply look at one number; you must drill down into every sub-folder and sum up every individual file.

The difficulty arises when your code treats "a single file" and "a folder containing files" as two completely different species that require different handling logic.

```java
import java.util.*;

class File {
    private String name;
    private long size;

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

    public long getSize() { return size; }
    public String getName() { return name; }
}

class Folder {
    private String name;
    private List<Object> contents = new ArrayList<>();

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

    public void add(Object item) { contents.add(item); }
    public String getName() { return name; }
    public List<Object> getContents() { return contents; }
}

class FileSystemService {
    public long calculateTotalSize(Object item) {
        if (item instanceof File) {
            return ((File) item).getSize();
        } else if (item instanceof Folder) {
            long total = 0;
            for (Object content : ((Folder) item).getContents()) {
                total += calculateTotalSize(content);
            }
            return total;
        }
        throw new IllegalArgumentException("Unknown type");
    }
}
```

The current implementation suffers from a "Switch Statements" smell (specifically, using `if/else` to check types). Every time the `FileSystemService` wants to perform an operation like `calculateTotalSize`, it must ask: "Are you a File? If so, do this. Are you a Folder? If so, loop through your contents and ask them for their size."

As the hierarchy grows deeper or more complex, these manual type-checks become a maintenance nightmare. To fix this, we use the refactoring called **Replace Conditional with Polymorphism**. Instead of the service deciding how to handle each type, we let the objects decide for themselves through a shared interface.

```mermaid
classDiagram
    FileSystemService ..> Object : uses
    Object <|-- File
    Object <|-- Folder
    Folder *-- Object : contains
```

## The Composite Pattern: Uniformity Through Recursion

The Composite pattern allows us to treat individual objects and compositions of objects uniformly. This is achieved by creating a shared interface that both the "Leaf" (the single item) and the "Composite" (the container) implement.

### The Anatomy of a Composite

To build this, we need three specific roles:
1.  **Component**: An interface or abstract class that defines the common behavior (like `getSize()` and `getName()`) for both items and containers.
2.  **Leaf**: A concrete class that represents an individual item with no children (e.g., a `File`).
3.  **Composite**: A concrete class that contains a collection of Components (e.g., a `Folder`).

When you call `getSize()` on a Composite, it doesn't need to know if its children are files or other folders; it simply tells every child: "Give me your size." This is called **recursive composition**, where a structure is built by nesting objects within themselves.

```java
import java.util.*;

interface FileSystemComponent {
    long getSize();
    String getName();
}

class File implements FileSystemComponent {
    private String name;
    private long size;

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

    @Override
    public long getSize() { return size; }
    @Override
    public String getName() { return name; }
}

class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> children = new ArrayList<>();

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

    public void add(FileSystemComponent component) { children.add(component); }
    @Override
    public long getSize() { 
        long total = 0;
        for (FileSystemComponent child : children) {
            total += child.getSize();
        }
        return total;
    }
    @Override
    public String getName() { return name; }
}

class FileSystemService {
    public long calculateTotalSize(FileSystemComponent component) {
        return component.getSize();
    }
}
```

In this refactored version, the `FileSystemService` is incredibly simple. It no longer cares about types; it only cares that the object passed to it satisfies the `FileSystemComponent` interface. The complexity of traversing the tree is moved out of the service and into the objects themselves.

When we call `getSize()` on a `Folder`, it triggers a chain reaction. The folder asks its children for their sizes, and if those children are also folders, they ask *their* children, until the calls reach the `File` objects at the bottom of the tree.

```mermaid
sequenceDiagram
    participant F as Folder
    participant C1 as FileComponent (Leaf)
    participant C2 as Folder (Composite)
    participant L as File (Leaf)

    F ->> C1: getSize()
    C1 -->> F: size

    F ->> C2: getSize()
    C2 ->> L: getSize()
    L -->> C2: size
    C2 -->> F: totalSize
```

## When to Use It, and When Not To

The Composite pattern is highly effective when your data naturally forms a tree structure—such as organizational charts, UI widget hierarchies, or XML documents. It provides high uniformity, allowing you to add new types of components without ever changing the code that uses them (adhering to the Open-Closed Principle).

However, there is a tension between **uniformity** and **interface integrity**. By forcing both Files and Folders to implement the same interface, you face a choice:
*   You can put structural methods like `add()` or `remove()` in the base `FileSystemComponent` interface. This makes everything uniform, but it creates a "Uniformity Trap" where a single `File` object is forced to implement an `add()` method that makes no sense and throws an error.
*   Alternatively, you can keep structural methods out of the base interface. This keeps your `Leaf` classes clean, but it means you might have to perform type-casting again if you want to add a file specifically to a folder.

Choose the approach that best fits how much safety you need versus how much simplicity you desire in your hierarchy.

## Takeaways

- Use the Composite pattern when you need to represent "part-whole" hierarchies where clients should treat individual items and collections identically.
- Replace type-checking `if/else` chains with polymorphic method calls defined in a shared interface.
- Be wary of adding management methods (like `add()` or `remove()`) to your base interface, as this can force leaf objects to implement meaningless behavior.

## Usage

**Demonstrating tree traversal via the Composite pattern**

```java
Folder root = new Folder("root");
root.add(new File("config.txt", 50));
Folder sub = new Folder("docs");
sub.add(new File("resume.pdf", 150));
root.add(sub);

long totalSize = service.calculateTotalSize(root); // 200
```
