Composite design pattern in java

In this article, I’ll introduce the Composite Design Pattern. This pattern is used to represent part-whole hierarchies.

About the Composite design pattern

Let’s see what the Gang of Four (GoF) tells us about this pattern:

“Compose objects into a tree structure to represent part-whole hierarchies. Composite lets the client treat individual objects and compositions of objects uniformly.”

In this pattern, the client uses the component interface to interact with objects which are part of the composition. You can imagine the composite hierarchy as a tree where there are leaves and composites, and the requests are sent through this tree.

If the recipient of the call is a leaf then the request is handled directly in this leaf. If the recipient is a composite then this composite forwards the requests to its children, alternatively, this composite can perform additional operations before and after forwarding.

Example

One good example of this pattern can be a company structure. For example, the first entry point is the VP of Marketing who wants a new feature/software developed. So he (or she) calls up the VP of Software Development to implement this feature. The VP calls up the managers to give him a time/cost estimate. These managers forward this request to their managers or developers. When the responses get back they are returned to the VP of Software Development who answers the VP of Marketing.

Let’s define the common interface of Employees. This interface defines the methods each employee of the company has to implement.

/**
 * Interface to have a hierarchy of Employees.
 *
 * @author GHajba
 */
public interface Employee {

  /** @return the name of the employee */
  String getName();

  /** @param e add this employee to the list of employees */
  void add(Employee e);

  /** @param e remove this employee from the list of employees */
  void remove(Employee e);

  /** @return the list of employees */
  List<Employee> getEmployees();

  /**
   * This method estimates the costs in ManDays for the given project. Managers delegate this
   * request to their employees, developers return an estimate.
   *
   * @param projectDescription
   * @return
   */
  int estimateProject(String projectDescription);
}

After this, I created the hierarchy structure of the company: VP, SeniorManager. TeamLeader and Developer, where developers are the leaf nodes. To use more of the DRY (Don’t Repeat Yourself) principle I introduced a common Manager class which is abstract.

/**
 * This abstract class implements the commmon functionality along all managers and gives them
 * default methods which "lazy" implementations do not have to cover.
 *
 * @author GHajba
 */
public abstract class Manager implements Employee {
  List<Employee> employees = new ArrayList<>();
  String name;

  public Manager(String name) {
    this.name = name;
  }

  @Override
  public List<Employee> getEmployees() {
    return this.employees;
  }

  @Override
  public void add(Employee e) {
    if (e != null) {
      this.employees.add(e);
    }
  }

  @Override
  public void remove(Employee e) {
    if (e != null) {
      this.employees.remove(e);
    }
  }

  @Override
  public int estimateProject(String projectDescription) {
    if (this.employees.isEmpty()) {
      return 0;
    }
    return Math.round(
        this.employees.stream()
                .mapToInt(
                    e -> {
                      System.out.println(e);
                      return e.estimateProject(projectDescription);
                    })
                .sum()
            / this.employees.size());
  }

  @Override
  public String getName() {
    return this.name;
  }
}

/**
 * Simple implementation of a VP
 *
 * @author GHajba
 */
public class VP extends Manager {

  public VP(String name) {
    super(name);
  }

  @Override
  public String toString() {
    return "I am " + getName() + ", VP";
  }

  /** VP doubles the estimated amount. */
  @Override
  public int estimateProject(String projectDescription) {
    System.out.println("I am " + getName() + ", the VP, and calling for an estimate...");
    final int projectEstimate = super.estimateProject(projectDescription);
    System.out.println("Original estimate: " + projectEstimate);
    return Math.toIntExact(Math.round(projectEstimate * 2));
  }
}

/**
 * Simple implementation of a Senior Manager
 *
 * @author GHajba
 */
public class SeniorManager extends Manager {

  public SeniorManager(String name) {
    super(name);
  }

  /** Senior Managers add 10% to the estimate of the team. */
  @Override
  public int estimateProject(String projectDescription) {
    return Math.toIntExact(Math.round(super.estimateProject(projectDescription) * 1.1));
  }

  @Override
  public String toString() {
    return "I am " + getName() + ", Senior Manager";
  }
}

/**
 * Simple implementation of a Team Leader
 *
 * @author GHajba
 */
public class TeamLeader extends Manager {

  public TeamLeader(String name) {
    super(name);
  }

  @Override
  public String toString() {
    return "I am " + getName() + ", Team Leader";
  }
}

As you can see, the Senior Managers and VPs implement their own version of the estimateProject method where they multiply the already estimated time by some factor. Team Leaders trust the developers.

The Developer class has to implement some not relevant methods because they are Employees too. However, because they are no managers they do not have employees so they cannot add or remove employees from their list — but they have to implement the estimateProject because developers are the leaves of this hierarchy. Here I have created an application to display these features using the public static void main application.

/**
 * Implementation of a plain-old Developer.
 *
 * @author GHajba
 *
 */
public class Developer implements Employee {

  String name;
Now let

  public Developer(String name) {
    this.name = name;
  }

  @Override
  public String getName() {
    return this.name;
  }

  @Override
  public void add(Employee e) {
  }

  @Override
  public void remove(Employee e) {
  }

  @Override
  public List<Employee> getEmployees() {
    return null;
  }

  @Override
  public int estimateProject(String projectDescription) {
    return new Random().nextInt(24);
  }
}

    @Override
  public String toString() {
    return "I am " + getName() + ", Developer";
  }'s implement a structure and see how the pattern works:

/**
 * This is the main entry point of the application.
 *
 * @author GHajba
 *
 */
public class Composite {
  public static void main(String... args) {
    final Developer d1 = new Developer("Jack");
    final Developer d2 = new Developer("Jill");
    final Developer d3 = new Developer("Brian");
    final Developer d4 = new Developer("Bob");

    final Manager m1 = new TeamLeader("Marc");
    final Manager m2 = new TeamLeader("Christian");
    final Manager m3 = new TeamLeader("Phil");
    m1.add(d3);
    m1.add(d2);
    m2.add(d1);
    m3.add(d4);

    final Manager m4 = new SeniorManager("Harald");
    final Manager m5 = new SeniorManager("Klaus");

    m4.add(m3);
    m4.add(m2);
    m5.add(m1);

    final VP vp = new VP("Joseph");
    vp.add(m4);
    vp.add(m5);

    System.out.println("Our estimate is: " + vp.estimateProject("New exotic feature"));
  }
}

As you can see, this example requires Java 8 at least because it utilizes the new Stream API and lambdas. And here is the result of a particular run:

I am Joseph, the VP, and calling for an estimate…
I am Harald, Senior Manager
I am Phil, Team Leader
I am Bob, Developer
I am Christian, Team Leader
I am Jack, Developer
I am Klaus, Senior Manager
I am Marc, Team Leader
I am Brian, Developer
I am Jill, Developer
Original estimate: 9
Our estimate is: 18

This example output shows the tree structure where it starts with the VP and goes down for each node in the hierarchy. I could do some more fancy printing but I think you get the idea of how this pattern works.

Disadvantages?

When the tree-structure is defined the composite architecture makes the tree-structure general and this makes the leaf objects have empty methods (or which just simply return nothing valuable) like the Developer class in the example.

Conclusion

This pattern can be used in situations when the problems represent a hierarchical relationship but they tend to have empty methods for leaf nodes in this hierarchy because of a common interface.

2 Comments

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.