Design Pattern (13) Composite Pattern: Tree Structure Unified Operation Design Guide

You can download the Design Pattern series code from this design_pattern repo.

Requirements

We received a requirement: implement a file system where directories can contain files or subdirectories, and provide a unified operation interface to list directory contents. This system should support the following features:

  • Support tree structure representation.
  • Can operate on individual files and directories.
  • No need for major code modifications when adding new files or directories.

Object-Oriented Analysis (OOA)

After understanding the requirements, let’s quickly implement object-oriented analysis!

Identifying Forces

Without using design patterns, the above requirements may encounter the following problems:

  1. Tight Coupling:
    • Operation logic for individual files and directory collections is scattered across multiple classes, making system maintenance difficult.
  2. Code Duplication:
    • Each time operating on directory contents, files and subdirectories need to be handled separately, causing similar logic to be repeated in multiple places.
  3. Difficulty in Extending:
    • When adding new file or directory types, major code modifications are needed, affecting system stability.
  4. Lack of Flexibility:
    • The operation layer needs to clearly distinguish between individual files and directory collections, increasing code complexity.

Applying Composite Pattern (Solution) to Achieve New Context (Resulting Context)

After completing OOA, identifying Forces, and understanding the entire Context, we can apply the Composite Pattern to solve this problem.

Let’s first look at the UML of the Composite Pattern:

Three Core Roles of Composite Pattern:

1. Component (Component Interface)

  • Defines common interface for all components (leaves and composites)
  • Provides consistent operation method for clients
  • In our file system, this is FileSystemComponent

2. Leaf (Leaf Node)

  • Represents terminal nodes in the tree structure that cannot contain other components
  • Implements basic behavior of the Component interface
  • In our example, this is the individual file File

3. Composite (Composite Node)

  • Represents container nodes that can contain child components
  • Implements Component interface and delegates operations to child components
  • This is our directory Directory, which can contain files and subdirectories

Applying to Our File System

Now let’s apply the Composite Pattern to the file system design:

Object-Oriented Programming (OOP)

[Component: FileSystemComponent]

abstract class FileSystemComponent(val name: String) {
    open fun display(indent: String = "") {
        println("$indent$name")
    }

    open fun add(component: FileSystemComponent) {
        throw UnsupportedOperationException("Cannot add component to a leaf.")
    }

    open fun remove(component: FileSystemComponent) {
        throw UnsupportedOperationException("Cannot remove component from a leaf.")
    }
}

[Leaf: File]

class File(name: String) : FileSystemComponent(name) {
    override fun display(indent: String) {
        println("$indent- File: $name")
    }
}

[Composite: Directory]

class Directory(name: String) : FileSystemComponent(name) {
    private val children = mutableListOf<FileSystemComponent>()

    override fun add(component: FileSystemComponent) {
        children.add(component)
    }

    override fun remove(component: FileSystemComponent) {
        children.remove(component)
    }

    override fun display(indent: String) {
        println("$indent+ Directory: $name")
        children.forEach { it.display("$indent  ") }
    }
}

[Client]

fun main() {
    // Build Directories and files
    val root = Directory("Root")
    val folder1 = Directory("Folder1")
    val folder2 = Directory("Folder2")

    val file1 = File("File1.txt")
    val file2 = File("File2.txt")
    val file3 = File("File3.txt")

    // Add files & directories into directories
    root.add(folder1)
    root.add(file1)

    folder1.add(folder2)
    folder1.add(file2)

    folder2.add(file3)

    // display file structure
    root.display()
}

[Output]

+ Directory: Root
  + Directory: Folder1
    + Directory: Folder2
      - File: File3.txt
    - File: File2.txt
  - File: File1.txt

Conclusion

By applying the Composite Pattern, we successfully achieved unified operations for individual files and directory collections. We effectively reduced system coupling and provided efficient extensibility. When new file types or directory structures need to be added, no major modifications to existing code are required. Through this pattern, developers can handle tree structure logic in a concise and consistent manner, improving program flexibility and maintainability.

Series Navigation

Structural Design Pattern Series

  • Adapter Pattern - Making incompatible interfaces work together
  • Bridge Pattern - Separating abstraction from implementation, supporting independent evolution
  • Decorator Pattern - Dynamically adding object functionality without modifying structure
  • Facade Pattern - Providing unified interface to simplify complex subsystems
  • Flyweight Pattern - Efficiently managing memory usage of large numbers of similar objects
  • Proxy Pattern - Controlling resource access through smart proxy objects

Behavioral Design Pattern Series

Creational Design Pattern Basics

Through the Composite Pattern, we mastered unified operation techniques for tree structures. In the next article on the Decorator Pattern, we will explore how to dynamically extend object functionality through wrapping techniques.




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Claude Code 使用技巧與最佳實踐 - Tips and Best Practices
  • 🤖 AI Agent Series (Part 1): Understanding the Core Interaction Logic of LLM, RAG, and MCP
  • 💡 Managing Multiple GitHub Accounts on One Computer: The Simplest SSH Configuration Method
  • 🚀 How to Use Excalidraw AI to Quickly Generate Professional Diagrams and Boost Work Efficiency!
  • Complete macOS Development Environment Setup Guide: Mobile Development Toolchain Configuration Tutorial