The Composite Pattern in Java

Introduction to the Composite Pattern

Design pattern in a simple meaning, is a way to design reusable object-oriented code. We can think of ways to design our classes, interfaces, enums and their members so that the code is reusable but flexible.

There are 23 most important design patterns, pioneered by the book Design Patterns: Elements of Reusable Object-Oriented Software, written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides or Gang of Four for short. We will cover these design pattern one by one.

The Composite pattern composes objects into tree structures. It allows us to treat each single object and the composition of objects uniformly. To do that the Composite pattern defines these elements:

– The leaf: we remember the basic DSA right? The leaf is a node in the tree without a child. In the Composite pattern, a leaf is a basic element.

– The composite: It must have at least one child. Note that the composite can have children that are also composites.

– The base component: normally an interface (or an abstract class will also do the job) that defines a set of operations which will be implemented by both the composites and the leaves in the tree.

Implementation of Composite Pattern in Java

Ok, nuf said, we will do an example right now. Suppose that we have the leaf class as Developer class in src folder (yep, the developers like us are normally at the bottom tier, the brutal truth 😀 ). And next we have the TeamLeader class which is a composite that has some Developer objects reporting to him/her. At the top we have the Supervisor class which is a composite that have some TeamLeader objects under his/her belt 😀

All of these classes extend the base component, the Employee class. Now in the src folder we create the Employee.java:

public abstract class Employee {
  private final String name;
  private final int salary;
  public Employee(String name, int salary) {
    this.name = name;
    this.salary = salary;
  }
  public String getName() {
    return name;
  }
  public int getSalary() {
    return salary;
  }
  public abstract void add(Employee emp);
  public abstract void print();
}

Ok, this is the base component for both TeamLeader and Developer classes. Now the leaf class

public class Developer extends Employee{
  public Developer(String name, int salary) {
    super(name, salary);
  }
  public void print(){
    System.out.print("The underling: ");
    System.out.println(this.getName() +
        " - salary: " + this.getSalary());
  }
  //as a leaf, this method does nothing
  public void add(Employee emp) {}
}

Not much about the bottom-tier class right? 😀 . We move on to the important one, the TeamLeader class:

import java.util.*;
public class TeamLeader extends Employee{
  //composition of Developer objects
  List<Employee> devs = new ArrayList<>();
  public TeamLeader(String name, int salary) {
    super(name, salary);
  }
  public void print(){
    System.out.print("The underboss: ");
    System.out.println(this.getName() +
        " - salary: " + this.getSalary());
    devs.forEach(Employee::print);
  }
  public void add(Employee emp) {
    devs.add(emp);
  }
}

The TeamLeader class has children that are Developer objects, grouping into a list. Next at the top of both TeamLeader and Developer:

import java.util.*;
public class Supervisor extends Employee{
  List<Employee> leaders = new ArrayList<>();
  public Supervisor(String name, int salary) {
    super(name, salary);
  }
  public void add(Employee emp) {
    leaders.add(emp);
  }
  public void print() {
    System.out.print("The boss: ");
    System.out.println(this.getName() +
        " - salary: " + this.getSalary());
    leaders.forEach(Employee::print);
  }
}

Now we can test our composite pattern in Test.java:

public class Test {
  public static void main(String[] args){
    var sup = new Supervisor("Minerva", 9_000);
    var ld = new TeamLeader("Hermione", 5_000);
    var dev1 = new Developer("Harry", 2_500);
    var dev2 = new Developer("Ron", 2_000);
    //add the team leader to the supervisor
    sup.add(ld);
    //ad devs to team
    ld.add(dev1);
    ld.add(dev2);
    //output all
    sup.print();
  }
}

And the output will be the structure of the tree:

The boss: Minerva - salary: 9000
The underboss: Hermione - salary: 5000
The underling: Harry - salary: 2500
The underling: Ron - salary: 2000

Well, poor Ron :D, anyway we move on to the next pattern, the proxy.

Leave a Reply