DEV Community

Vignesh Athiappan
Vignesh Athiappan

Posted on

Composite: The Folders-and-Files Pattern

Composite: The Folders-and-Files Pattern

Look at your computer's file system. A file has a size. A folder contains files — and other folders, which contain more files and folders, as deep as you like.

Now ask: "What's the size of this folder?"

You don't care whether it holds files or folders. You just ask the folder, and it figures it out — adding up everything inside, however deep it goes. A folder with one file and a folder with 500 nested subfolders answer the same question the same way.

That's the Composite pattern: treat a single object and a whole tree of objects identically. A leaf (file) and a branch (folder) respond to the same call through the same interface.

The problem it solves

Without it, your code fills up with "is this one thing or many things?" checks:

// Constantly asking: am I dealing with a file or a folder?
if (item is File) total += ((File)item).Size;
else if (item is Folder)
    foreach (var child in ((Folder)item).Children)
        // ...now recurse manually, checking types again
Enter fullscreen mode Exit fullscreen mode

Type-checks everywhere. Every new level of nesting means more branches. Fragile and ugly.

The Composite way

Everyone — file and folder — shares one interface:

public interface IFileSystemItem
{
    int GetSize();
}
Enter fullscreen mode Exit fullscreen mode

The leaf — a single item with nothing inside:

public class FileItem : IFileSystemItem
{
    private readonly int _size;
    public FileItem(int size) => _size = size;
    public int GetSize() => _size;      // just returns its own size
}
Enter fullscreen mode Exit fullscreen mode

The composite — holds children, which are themselves IFileSystemItem:

public class Folder : IFileSystemItem
{
    private readonly List<IFileSystemItem> _children = new();
    public void Add(IFileSystemItem item) => _children.Add(item);

    public int GetSize()
    {
        int total = 0;
        foreach (var child in _children)
            total += child.GetSize();   // ask each child — don't care what it is
        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice the loop calls child.GetSize() without ever asking "file or folder?" If the child is a file, it returns its size. If it's a folder, it recurses into its own children. The recursion is invisible and automatic.

var root = new Folder();
root.Add(new FileItem(100));
root.Add(new FileItem(200));

var sub = new Folder();
sub.Add(new FileItem(50));
sub.Add(new FileItem(75));
root.Add(sub);              // a folder inside a folder — no problem

Console.WriteLine(root.GetSize());   // 425 — one call walks the whole tree
Enter fullscreen mode Exit fullscreen mode

But where do you actually use this?

The file/folder example explains the mechanism. Here's where it shows up in real work:

  • UI component trees. Angular, React, and WPF are Composite. A component contains components; calling "render" on the root renders the whole tree. If you build a reusable component library, you're building composites.
  • Nested menus and navigation. A menu item (leaf) and a menu group (composite) both need Render() and IsVisible(user). One recursive call renders the entire sidebar and respects permissions at every level.
  • Org and approval hierarchies. A manager contains employees; some employees are managers. GetAllReports() on the top boss walks the whole tree with zero type-checking.
  • Rule and permission trees. A rule can be a single condition or a group of rules (ALL of these / ANY of these), nested to any depth. A condition and a group both answer Evaluate(user). This is how validation and access-policy engines are built.
  • Document structures. JSON, XML, HTML, folder trees — anything you'd draw as a tree. Parsing, sizing, searching, exporting: all one recursive call.

When NOT to use it

Be honest about your data. Composite earns its place only when you have a genuine tree — things containing things of their own type, to arbitrary depth. If your data is a flat list, a plain List<T> is fine. Don't force a tree where there isn't one.

The test: "Am I running the same operation over one item and over an entire subtree, and I want both to be the same line of code?" If yes, Composite. If no, skip it.

One line to remember

Composite = folders and files. A single item and a whole tree of items answer the same call the same way — and recursion handles itself.


Part 8 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, and Proxy. Next up: Observer — the notification pattern.

Top comments (1)

Collapse
 
algorhymer profile image
algorhymer

Canaidh sinn Rose Tree ris.