Menu
Coddy logo textTech

Composite Pattern

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 99 of 110.

The Composite pattern lets you treat individual objects and groups of objects uniformly. It composes objects into tree structures where both single elements and collections of elements share the same interface. This is perfect for representing hierarchies like file systems, organization charts, or UI components.

The pattern has two types of participants: Leaf nodes (individual objects with no children) and Composite nodes (containers that hold other components). Both implement a common Component interface:

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

The Folder class can contain both File objects and other Folder objects. When you call getSize() on a folder, it recursively calculates the total size of all its contents. Client code doesn't need to know whether it's working with a single file or an entire folder hierarchy - both respond to the same methods.

challenge icon

Challenge

Easy

Let's build an organization chart system using the Composite pattern! You'll create a structure where both individual employees and departments (which contain employees and other departments) can be treated uniformly - perfect for representing company hierarchies.

You'll organize your code into two files:

  • organization.dart: This file contains your component interface and both leaf and composite classes. Create an abstract class OrganizationUnit with a name getter, a getSalaryBudget() method that returns an int, and a display([String indent = '']) method for showing the hierarchy. Build an Employee class (the leaf) that implements OrganizationUnit. It should have a name and salary, with getSalaryBudget() returning the employee's salary. Its display() should print [indent]- [name] ($[salary]). Then create a Department class (the composite) that also implements OrganizationUnit. It should have a name and maintain a list of OrganizationUnit children. Add an add() method to include employees or sub-departments. Its getSalaryBudget() should recursively sum all children's budgets. Its display() should print [indent]+ [name]/ followed by displaying each child with increased indentation (add two spaces).
  • main.dart: Import your organization file and build a company structure. Create an Engineering department containing two employees: Alice with salary 80000 and Bob with salary 75000. Create a QA department with one employee: Carol with salary 65000. Now create a Technology department and add both Engineering and QA as sub-departments. Call display() on the Technology department, then print Total Budget: $ followed by the total salary budget.

The beauty of the Composite pattern is that Technology.getSalaryBudget() automatically calculates the total across all nested departments and employees - the client code doesn't need to know the internal structure!

Expected output:

+ Technology/
  + Engineering/
    - Alice ($80000)
    - Bob ($75000)
  + QA/
    - Carol ($65000)
Total Budget: $220000

Cheat sheet

The Composite pattern allows you to treat individual objects and groups of objects uniformly by composing them into tree structures where both single elements and collections share the same interface.

The pattern consists of:

  • Component: An abstract interface defining common operations
  • Leaf: Individual objects with no children
  • Composite: Containers that hold other components (leaves or composites)

Example: File System

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

The Folder class can contain both File objects and other Folder objects. When you call getSize() on a folder, it recursively calculates the total size of all its contents. Client code treats individual files and entire folder hierarchies uniformly.

Try it yourself

import 'organization.dart';

void main() {
  // TODO: Create an Engineering department
  // TODO: Add Alice with salary 80000
  // TODO: Add Bob with salary 75000
  
  // TODO: Create a QA department
  // TODO: Add Carol with salary 65000
  
  // TODO: Create a Technology department
  // TODO: Add Engineering and QA as sub-departments
  
  // TODO: Call display() on the Technology department
  
  // TODO: Print "Total Budget: $" followed by the total salary budget
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming