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 individual elements and composite groups uniformly, reducing complexity.

  • Example: A form section can contain both fields and nested sections without the client worrying about their types.

2. Facilitates Recursive Structures

  • It supports recursive tree-like structures, where composites can contain other composites.

  • Example: A university form can have nested sections, which can themselves contain sub-sections.

3. Enhances Maintainability

  • Since components share a common interface, adding new form field types (e.g., DatePickerField) requires no changes to existing code.

4. Adheres to SOLID Principles

  • Open/Closed Principle (OCP) → You can extend the system with new components without modifying existing code.

  • Single Responsibility Principle (SRP) → Each component (e.g., TextField, CheckboxField) has its own responsibility.

5. Supports Dynamic Composition

  • We can dynamically construct complex structures at runtime.

  • Example: A multi-step form builder where sections are added based on user choices.

How does the Composite Pattern apply to our code?

  • In the University Form application, we have both individual fields (like TextField, CheckboxField) and sections (FormSection) that group multiple fields.

  • The entire form behaves like a tree, where sections can contain both fields and other sections.

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

//FormSection is a composite that groups multiple form fields together.

//We add individual fields (nameField, emailField) to the section without treating 
//them differently.

//The form is structured like a tree, with sections (whole) containing form fields (parts).

//A section can contain fields, and a form can contain multiple sections.

//The entire form is a nested structure, just like a tree.

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

//mainForm is the whole, and it contains parts (personalSection, preferenceSection).
//These sections themselves contain smaller parts (form fields).

//Every form component (TextField, CheckboxField, DropdownField, FormSection) 
//implements the same FormComponent interface.

//This ensures that both individual fields and composite sections can be 
//treated in the same way.

export interface FormComponent {
  render(): void;
}

//Every form field (leaf) and form section (composite) implements this interface.

//Because of this, we can call .render() on both form fields and form sections 
//without checking their type.

The GITHUB code link is here