Composite Pattern for Tree Structures in Java

The Composite Pattern is a structural design pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

Basic Composite Pattern Implementation

1. Component Interface

// Component interface - defines common operations for both leaf and composite
public interface FileSystemComponent {
String getName();
long getSize();
void display(String indent);
boolean isDirectory();
void addComponent(FileSystemComponent component);
void removeComponent(FileSystemComponent component);
List<FileSystemComponent> getChildren();
}
// Abstract base class providing default implementations
public abstract class AbstractFileSystemComponent implements FileSystemComponent {
protected String name;
public AbstractFileSystemComponent(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isDirectory() {
return false;
}
// Default implementations for leaf nodes
@Override
public void addComponent(FileSystemComponent component) {
throw new UnsupportedOperationException("Cannot add to a leaf node");
}
@Override
public void removeComponent(FileSystemComponent component) {
throw new UnsupportedOperationException("Cannot remove from a leaf node");
}
@Override
public List<FileSystemComponent> getChildren() {
return Collections.emptyList();
}
}

2. Leaf Class

// Leaf class - represents individual objects in the composition
public class File extends AbstractFileSystemComponent {
private long size;
private String content;
public File(String name, long size) {
super(name);
this.size = size;
}
public File(String name, long size, String content) {
super(name);
this.size = size;
this.content = content;
}
@Override
public long getSize() {
return size;
}
@Override
public void display(String indent) {
System.out.println(indent + "πŸ“„ " + name + " (" + size + " bytes)");
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public void setSize(long size) {
this.size = size;
}
}

3. Composite Class

import java.util.ArrayList;
import java.util.List;
// Composite class - represents compositions of components
public class Directory extends AbstractFileSystemComponent {
private List<FileSystemComponent> children;
public Directory(String name) {
super(name);
this.children = new ArrayList<>();
}
@Override
public long getSize() {
long totalSize = 0;
for (FileSystemComponent component : children) {
totalSize += component.getSize();
}
return totalSize;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public void display(String indent) {
System.out.println(indent + "πŸ“ " + name + " (" + getSize() + " bytes)");
for (FileSystemComponent component : children) {
component.display(indent + "  ");
}
}
@Override
public void addComponent(FileSystemComponent component) {
children.add(component);
}
@Override
public void removeComponent(FileSystemComponent component) {
children.remove(component);
}
@Override
public List<FileSystemComponent> getChildren() {
return new ArrayList<>(children);
}
// Additional directory-specific operations
public int getFileCount() {
int count = 0;
for (FileSystemComponent component : children) {
if (!component.isDirectory()) {
count++;
} else {
count += ((Directory) component).getFileCount();
}
}
return count;
}
public int getDirectoryCount() {
int count = 0;
for (FileSystemComponent component : children) {
if (component.isDirectory()) {
count += 1 + ((Directory) component).getDirectoryCount();
}
}
return count;
}
public FileSystemComponent findComponent(String name) {
if (this.name.equals(name)) {
return this;
}
for (FileSystemComponent component : children) {
if (component.getName().equals(name)) {
return component;
}
if (component.isDirectory()) {
FileSystemComponent found = ((Directory) component).findComponent(name);
if (found != null) {
return found;
}
}
}
return null;
}
}

Advanced Composite Pattern Examples

4. Organization Hierarchy Example

// Component interface for organization structure
public interface OrganizationComponent {
String getName();
String getRole();
double getSalary();
void display(String indent);
void add(OrganizationComponent component);
void remove(OrganizationComponent component);
List<OrganizationComponent> getSubordinates();
}
// Employee (Leaf)
public class Employee implements OrganizationComponent {
private String name;
private String role;
private double salary;
public Employee(String name, String role, double salary) {
this.name = name;
this.role = role;
this.salary = salary;
}
@Override
public String getName() {
return name;
}
@Override
public String getRole() {
return role;
}
@Override
public double getSalary() {
return salary;
}
@Override
public void display(String indent) {
System.out.println(indent + "πŸ‘€ " + name + " - " + role + " ($" + salary + ")");
}
@Override
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException("Cannot add to an employee");
}
@Override
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException("Cannot remove from an employee");
}
@Override
public List<OrganizationComponent> getSubordinates() {
return Collections.emptyList();
}
public void setSalary(double salary) {
this.salary = salary;
}
}
// Department (Composite)
public class Department implements OrganizationComponent {
private String name;
private List<OrganizationComponent> members;
public Department(String name) {
this.name = name;
this.members = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
@Override
public String getRole() {
return "Department";
}
@Override
public double getSalary() {
double totalSalary = 0;
for (OrganizationComponent member : members) {
totalSalary += member.getSalary();
}
return totalSalary;
}
@Override
public void display(String indent) {
System.out.println(indent + "🏒 " + name + " Department (Total Salary: $" + getSalary() + ")");
for (OrganizationComponent member : members) {
member.display(indent + "  ");
}
}
@Override
public void add(OrganizationComponent component) {
members.add(component);
}
@Override
public void remove(OrganizationComponent component) {
members.remove(component);
}
@Override
public List<OrganizationComponent> getSubordinates() {
return new ArrayList<>(members);
}
// Department-specific operations
public int getEmployeeCount() {
int count = 0;
for (OrganizationComponent member : members) {
if (member instanceof Employee) {
count++;
} else {
count += ((Department) member).getEmployeeCount();
}
}
return count;
}
public double getAverageSalary() {
int employeeCount = getEmployeeCount();
return employeeCount > 0 ? getSalary() / employeeCount : 0;
}
}

5. UI Components Example

// Component interface for UI elements
public interface UIComponent {
String getName();
void render();
void add(UIComponent component);
void remove(UIComponent component);
UIComponent getChild(int index);
List<UIComponent> getChildren();
}
// Base component class
public abstract class BaseUIComponent implements UIComponent {
protected String name;
protected List<UIComponent> children;
public BaseUIComponent(String name) {
this.name = name;
this.children = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
@Override
public void add(UIComponent component) {
children.add(component);
}
@Override
public void remove(UIComponent component) {
children.remove(component);
}
@Override
public UIComponent getChild(int index) {
return children.get(index);
}
@Override
public List<UIComponent> getChildren() {
return new ArrayList<>(children);
}
}
// Leaf components
public class Button extends BaseUIComponent {
private String label;
public Button(String name, String label) {
super(name);
this.label = label;
}
@Override
public void render() {
System.out.println("πŸ”„ Rendering Button: " + name + " [Label: " + label + "]");
}
public void click() {
System.out.println("βœ… Button '" + name + "' clicked!");
}
}
public class TextField extends BaseUIComponent {
private String placeholder;
private String value;
public TextField(String name, String placeholder) {
super(name);
this.placeholder = placeholder;
}
@Override
public void render() {
System.out.println("πŸ“ Rendering TextField: " + name + " [Value: " + value + ", Placeholder: " + placeholder + "]");
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public class Label extends BaseUIComponent {
private String text;
public Label(String name, String text) {
super(name);
this.text = text;
}
@Override
public void render() {
System.out.println("🏷️  Rendering Label: " + name + " [Text: " + text + "]");
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
// Composite components
public class Panel extends BaseUIComponent {
private String layout;
public Panel(String name, String layout) {
super(name);
this.layout = layout;
}
@Override
public void render() {
System.out.println("🎨 Rendering Panel: " + name + " [Layout: " + layout + "]");
for (UIComponent child : getChildren()) {
child.render();
}
}
public void addComponent(UIComponent component) {
add(component);
}
}
public class Form extends BaseUIComponent {
private String action;
public Form(String name, String action) {
super(name);
this.action = action;
}
@Override
public void render() {
System.out.println("πŸ“‹ Rendering Form: " + name + " [Action: " + action + "]");
for (UIComponent child : getChildren()) {
child.render();
}
}
public void submit() {
System.out.println("πŸš€ Submitting form: " + name);
// Collect data from form fields
collectFormData();
}
private void collectFormData() {
for (UIComponent child : getChildren()) {
if (child instanceof TextField) {
TextField textField = (TextField) child;
System.out.println("  πŸ“¦ " + textField.getName() + ": " + textField.getValue());
}
}
}
}

Enhanced Composite Pattern with Visitors

6. Visitor Pattern Integration

// Visitor interface for operations on composite structure
public interface FileSystemVisitor {
void visit(File file);
void visit(Directory directory);
}
// Concrete visitors
public class SizeCalculatorVisitor implements FileSystemVisitor {
private long totalSize = 0;
@Override
public void visit(File file) {
totalSize += file.getSize();
}
@Override
public void visit(Directory directory) {
// Directory size is calculated from children, so we don't add anything here
// But we need to visit all children
for (FileSystemComponent child : directory.getChildren()) {
if (child instanceof File) {
visit((File) child);
} else if (child instanceof Directory) {
visit((Directory) child);
}
}
}
public long getTotalSize() {
return totalSize;
}
}
public class SearchVisitor implements FileSystemVisitor {
private String searchTerm;
private List<FileSystemComponent> results;
public SearchVisitor(String searchTerm) {
this.searchTerm = searchTerm.toLowerCase();
this.results = new ArrayList<>();
}
@Override
public void visit(File file) {
if (file.getName().toLowerCase().contains(searchTerm)) {
results.add(file);
}
}
@Override
public void visit(Directory directory) {
if (directory.getName().toLowerCase().contains(searchTerm)) {
results.add(directory);
}
// Search in children
for (FileSystemComponent child : directory.getChildren()) {
if (child instanceof File) {
visit((File) child);
} else if (child instanceof Directory) {
visit((Directory) child);
}
}
}
public List<FileSystemComponent> getResults() {
return results;
}
}
// Enhanced component interface with accept method
public interface VisitableFileSystemComponent extends FileSystemComponent {
void accept(FileSystemVisitor visitor);
}
// Updated File class
public class VisitableFile extends File implements VisitableFileSystemComponent {
public VisitableFile(String name, long size) {
super(name, size);
}
public VisitableFile(String name, long size, String content) {
super(name, size, content);
}
@Override
public void accept(FileSystemVisitor visitor) {
visitor.visit(this);
}
}
// Updated Directory class
public class VisitableDirectory extends Directory implements VisitableFileSystemComponent {
public VisitableDirectory(String name) {
super(name);
}
@Override
public void accept(FileSystemVisitor visitor) {
visitor.visit(this);
}
@Override
public void addComponent(FileSystemComponent component) {
if (!(component instanceof VisitableFileSystemComponent)) {
throw new IllegalArgumentException("Component must be visitable");
}
super.addComponent(component);
}
}

Real-World Application: E-commerce Product Catalog

7. Product Catalog System

// Component interface for product catalog
public interface CatalogComponent {
String getName();
String getDescription();
double getPrice();
void display();
void add(CatalogComponent component);
void remove(CatalogComponent component);
List<CatalogComponent> getChildren();
}
// Product (Leaf)
public class Product implements CatalogComponent {
private String name;
private String description;
private double price;
private String sku;
public Product(String name, String description, double price, String sku) {
this.name = name;
this.description = description;
this.price = price;
this.sku = sku;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
@Override
public void display() {
System.out.println("πŸ›’ " + name + " - $" + price);
System.out.println("   πŸ“ " + description);
System.out.println("   🏷️  SKU: " + sku);
}
@Override
public void add(CatalogComponent component) {
throw new UnsupportedOperationException("Cannot add to a product");
}
@Override
public void remove(CatalogComponent component) {
throw new UnsupportedOperationException("Cannot remove from a product");
}
@Override
public List<CatalogComponent> getChildren() {
return Collections.emptyList();
}
// Product-specific methods
public String getSku() {
return sku;
}
public void setPrice(double price) {
this.price = price;
}
}
// Product Bundle (Composite)
public class ProductBundle implements CatalogComponent {
private String name;
private String description;
private List<CatalogComponent> products;
private double discount;
public ProductBundle(String name, String description, double discount) {
this.name = name;
this.description = description;
this.products = new ArrayList<>();
this.discount = discount;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
double totalPrice = 0;
for (CatalogComponent product : products) {
totalPrice += product.getPrice();
}
return totalPrice * (1 - discount);
}
public double getOriginalPrice() {
double totalPrice = 0;
for (CatalogComponent product : products) {
totalPrice += product.getPrice();
}
return totalPrice;
}
@Override
public void display() {
System.out.println("🎁 " + name + " - $" + getPrice() + " (Original: $" + getOriginalPrice() + ")");
System.out.println("   πŸ“ " + description);
System.out.println("   πŸ’° Discount: " + (discount * 100) + "%");
System.out.println("   πŸ“¦ Contains:");
for (CatalogComponent product : products) {
System.out.print("      ");
product.display();
}
}
@Override
public void add(CatalogComponent component) {
products.add(component);
}
@Override
public void remove(CatalogComponent component) {
products.remove(component);
}
@Override
public List<CatalogComponent> getChildren() {
return new ArrayList<>(products);
}
// Bundle-specific methods
public double getDiscount() {
return discount;
}
public void setDiscount(double discount) {
this.discount = discount;
}
public int getProductCount() {
int count = 0;
for (CatalogComponent component : products) {
if (component instanceof Product) {
count++;
} else {
count += ((ProductBundle) component).getProductCount();
}
}
return count;
}
}
// Category (Composite)
public class Category implements CatalogComponent {
private String name;
private String description;
private List<CatalogComponent> items;
public Category(String name, String description) {
this.name = name;
this.description = description;
this.items = new ArrayList<>();
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
throw new UnsupportedOperationException("Category doesn't have a price");
}
@Override
public void display() {
System.out.println("πŸ“‚ " + name + " Category");
System.out.println("   πŸ“ " + description);
System.out.println("   πŸ“Š Items: " + items.size());
for (CatalogComponent item : items) {
item.display();
}
}
@Override
public void add(CatalogComponent component) {
items.add(component);
}
@Override
public void remove(CatalogComponent component) {
items.remove(component);
}
@Override
public List<CatalogComponent> getChildren() {
return new ArrayList<>(items);
}
// Category-specific methods
public int getTotalProductCount() {
int count = 0;
for (CatalogComponent item : items) {
if (item instanceof Product) {
count++;
} else if (item instanceof ProductBundle) {
count += ((ProductBundle) item).getProductCount();
} else if (item instanceof Category) {
count += ((Category) item).getTotalProductCount();
}
}
return count;
}
}

Demo and Usage Examples

8. Comprehensive Demo Class

public class CompositePatternDemo {
public static void main(String[] args) {
System.out.println("=== File System Demo ===");
demoFileSystem();
System.out.println("\n=== Organization Structure Demo ===");
demoOrganizationStructure();
System.out.println("\n=== UI Components Demo ===");
demoUIComponents();
System.out.println("\n=== E-commerce Catalog Demo ===");
demoEcommerceCatalog();
System.out.println("\n=== Visitor Pattern Demo ===");
demoVisitorPattern();
}
private static void demoFileSystem() {
// Create files
FileSystemComponent file1 = new File("document.txt", 1024);
FileSystemComponent file2 = new File("image.jpg", 2048);
FileSystemComponent file3 = new File("data.csv", 512);
// Create directories
Directory documents = new Directory("Documents");
Directory pictures = new Directory("Pictures");
Directory root = new Directory("Root");
// Build structure
documents.addComponent(file1);
documents.addComponent(file3);
pictures.addComponent(file2);
root.addComponent(documents);
root.addComponent(pictures);
// Display structure
root.display("");
// Statistics
System.out.println("\nπŸ“Š Statistics:");
System.out.println("Total size: " + root.getSize() + " bytes");
System.out.println("File count: " + root.getFileCount());
System.out.println("Directory count: " + root.getDirectoryCount());
}
private static void demoOrganizationStructure() {
// Create employees
OrganizationComponent ceo = new Employee("Alice Johnson", "CEO", 200000);
OrganizationComponent cto = new Employee("Bob Smith", "CTO", 180000);
OrganizationComponent dev1 = new Employee("Charlie Brown", "Developer", 80000);
OrganizationComponent dev2 = new Employee("Diana Prince", "Senior Developer", 120000);
// Create departments
Department engineering = new Department("Engineering");
Department company = new Department("Company");
// Build structure
engineering.add(dev1);
engineering.add(dev2);
engineering.add(cto);
company.add(ceo);
company.add(engineering);
// Display structure
company.display("");
// Statistics
System.out.println("\nπŸ“Š Department Statistics:");
System.out.println("Total employees: " + company.getEmployeeCount());
System.out.println("Total salary: $" + company.getSalary());
System.out.println("Average salary: $" + company.getAverageSalary());
}
private static void demoUIComponents() {
// Create UI components
UIComponent button1 = new Button("submitBtn", "Submit");
UIComponent textField = new TextField("emailField", "Enter email");
UIComponent label = new Label("welcomeLabel", "Welcome!");
// Create containers
Panel mainPanel = new Panel("mainPanel", "Vertical");
Form loginForm = new Form("loginForm", "/login");
// Build UI hierarchy
loginForm.add(label);
loginForm.add(textField);
loginForm.add(button1);
mainPanel.add(loginForm);
// Render UI
mainPanel.render();
// Simulate form submission
((TextField) textField).setValue("[email protected]");
((Form) loginForm).submit();
}
private static void demoEcommerceCatalog() {
// Create products
CatalogComponent laptop = new Product("MacBook Pro", "High-performance laptop", 1999.99, "MBP-2023");
CatalogComponent mouse = new Product("Wireless Mouse", "Ergonomic wireless mouse", 49.99, "WM-001");
CatalogComponent keyboard = new Product("Mechanical Keyboard", "RGB mechanical keyboard", 129.99, "MK-002");
// Create bundles
ProductBundle officeBundle = new ProductBundle("Office Setup", "Complete office setup", 0.1);
officeBundle.add(laptop);
officeBundle.add(mouse);
officeBundle.add(keyboard);
// Create categories
Category electronics = new Category("Electronics", "Electronic devices and accessories");
electronics.add(laptop);
electronics.add(mouse);
electronics.add(keyboard);
electronics.add(officeBundle);
// Display catalog
electronics.display();
// Statistics
System.out.println("\nπŸ“Š Catalog Statistics:");
System.out.println("Total products: " + electronics.getTotalProductCount());
}
private static void demoVisitorPattern() {
// Create visitable file system
VisitableFileSystemComponent file1 = new VisitableFile("doc1.txt", 1024);
VisitableFileSystemComponent file2 = new VisitableFile("img1.jpg", 2048);
VisitableFileSystemComponent file3 = new VisitableFile("data1.csv", 512);
VisitableDirectory dir1 = new VisitableDirectory("Folder1");
VisitableDirectory root = new VisitableDirectory("Root");
dir1.addComponent(file1);
dir1.addComponent(file2);
root.addComponent(dir1);
root.addComponent(file3);
// Use SizeCalculatorVisitor
SizeCalculatorVisitor sizeVisitor = new SizeCalculatorVisitor();
root.accept(sizeVisitor);
System.out.println("Total size: " + sizeVisitor.getTotalSize() + " bytes");
// Use SearchVisitor
SearchVisitor searchVisitor = new SearchVisitor("doc");
root.accept(searchVisitor);
System.out.println("Search results for 'doc':");
for (FileSystemComponent result : searchVisitor.getResults()) {
System.out.println("  - " + result.getName());
}
}
}

Key Benefits of Composite Pattern

  1. Uniform Treatment: Clients can treat individual objects and compositions uniformly
  2. Simplified Client Code: Clients don't need to know whether they're dealing with leaf or composite nodes
  3. Easy to Add New Components: New component types can be added without changing existing code
  4. Flexible Structure: Tree structures can be built and modified dynamically
  5. Recursive Operations: Operations can be applied recursively throughout the tree structure

Common Use Cases

  • File Systems: Directories and files
  • GUI Components: Containers and individual elements
  • Organization Charts: Departments and employees
  • Product Catalogs: Categories, bundles, and individual products
  • Abstract Syntax Trees: In compilers and interpreters
  • Menu Systems: Nested menus and menu items

The Composite Pattern is particularly useful when you need to represent hierarchical structures and perform operations that need to be applied uniformly across both individual elements and entire compositions.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper