Composite Design Pattern
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:
Leaf Components – Individual form fields like TextField, CheckboxField, and DropdownField.
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
Scalability – Easily add new form fields and sections.
Uniform Treatment – Individual fields and sections are treated the same way.
Flexibility – New form structures can be created dynamically.
Reusability – Reuse sections across different forms.
Significance of the Composite Pattern
Works with Hierarchical Data
- Useful when dealing with nested structures like UI components, forms, file systems, organization charts, etc.
Uniformity in Object Handling
- Treats both individual elements and their compositions identically, simplifying code and improving maintainability.
Flexibility and Scalability
- Easy to add new types of components without modifying existing structures (Open-Closed Principle).
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