Skip to main content

Command Palette

Search for a command to run...

Composite Design Pattern

Updated
5 min readView as Markdown

The Composite Design Pattern is a structural pattern that allows us to build complex tree structures where individual objects and groups of objects are treated uniformly.

Concept

It provides a way to compose objects into tree structures to represent part-whole hierarchies. The key idea is that both individual objects (leaves) and groups of objects (composites) implement the same interface, allowing uniform treatment.

A simpler explanation

The Composite Design Pattern helps us to create nested structures where both single items and groups of items are treated the same way.

Key Idea:

Imagine we have a university form application. Some parts of the form have individual fields (like "Name" or "Email"), while others have sections (like "Personal Details" or "Preferences") that group multiple fields together.

With the Composite Pattern, both single fields and sections follow the same rules, making it easy to manage them as a whole. You can add, remove, or process them without worrying about whether they are a single field or a group.

In a University Form Filling App, different form fields (like text fields, checkboxes, and dropdowns) can be combined into sections or composite groups, allowing for a flexible and extensible form structure. The Composite Design Pattern is useful for this use-case because we need to treat individual objects and compositions of objects uniformly.

Implementation:

We will implement a FormComponent interface with two types of components:

  1. Leaf Components – Individual form fields like TextField, CheckboxField, and DropdownField.

  2. Composite Component – A FormSection that can contain multiple fields or even other sections.

Step 1 : Define the Component Interface

export interface FormComponent {
  render(): void;
}

Step 2: Create Leaf Components (Individual Form Fields)

// TextField.ts
import { FormComponent } from "./FormComponent";

export class TextField implements FormComponent {
  constructor(private label: string, private value: string = "") {}

  render(): void {
    console.log(`TextField: ${this.label} = ${this.value}`);
  }
}

// CheckboxField.ts
import { FormComponent } from "./FormComponent";

export class CheckboxField implements FormComponent {
  constructor(private label: string, private checked: boolean = false) {}

  render(): void {
    console.log(`CheckboxField: ${this.label} = ${this.checked}`);
  }
}

// DropdownField.ts
import { FormComponent } from "./FormComponent";

export class DropdownField implements FormComponent {
  constructor(private label: string, private options: string[], private selected?: string) {}

  render(): void {
    console.log(`DropdownField: ${this.label} = ${this.selected ?? "Not selected"}`);
  }
}

Step 3: Create Composite Component (Form Section)

// FormSection.ts
import { FormComponent } from "./FormComponent";

export class FormSection implements FormComponent {
  private components: FormComponent[] = [];

  constructor(private title: string) {}

  addComponent(component: FormComponent): void {
    this.components.push(component);
  }

  render(): void {
    console.log(`== Section: ${this.title} ==`);
    this.components.forEach((component) => component.render());
  }
}

Step 4: Implement the Form and Render It

import { TextField } from "./TextField";
import { CheckboxField } from "./CheckboxField";
import { DropdownField } from "./DropdownField";
import { FormSection } from "./FormSection";

function main() {

    // Create form fields
    const nameField = new TextField("Full Name");
    const emailField = new TextField("Email");
    const acceptTerms = new CheckboxField("Accept Terms", false);
    const departmentDropdown = new DropdownField("Department", ["CS", "Math", "Physics"], "CS");

    // Create sections
    const personalSection = new FormSection("Personal Details");
    personalSection.addComponent(nameField);
    personalSection.addComponent(emailField);

    const preferenceSection = new FormSection("Preferences");
    preferenceSection.addComponent(departmentDropdown);
    preferenceSection.addComponent(acceptTerms);

    // Main Form
    const mainForm = new FormSection("University Admission Form");
    mainForm.addComponent(personalSection);
    mainForm.addComponent(preferenceSection);

    // Render the form
    mainForm.render();

}

main();

Advantages of Using Composite Pattern

  1. Scalability – Easily add new form fields and sections.

  2. Uniform Treatment – Individual fields and sections are treated the same way.

  3. Flexibility – New form structures can be created dynamically.

  4. Reusability – Reuse sections across different forms.

Significance of the Composite Pattern

  1. Works with Hierarchical Data

    • Useful when dealing with nested structures like UI components, forms, file systems, organization charts, etc.
  2. Uniformity in Object Handling

    • Treats both individual elements and their compositions identically, simplifying code and improving maintainability.
  3. Flexibility and Scalability

    • Easy to add new types of components without modifying existing structures (Open-Closed Principle).
  4. Promotes Reusability

    • We can define smaller reusable components and compose them into larger structures dynamically.

Benefits of the Composite Pattern

1. Simplifies Client Code

  • Clients can work with