Peter Minesu
Peter Minesu

Reputation: 33

How to implement a composite pattern in Java?

I want to implement a composite pattern in Java in order to map a software development organization. So, let's assume there are multiple project managers and multiple developers. Each developer is assigned to exactly one project manager and each developer is able to code in various programming languages. The project managers lead the developers and know exactly their workload.

I am not a hundred percent sure about this design pattern, but I think that it is the perfect use case for this scenario, isn't it?

The result should be as follows:

I want to query the project manager to check the workload of all developers which are able to code in a specific programming language, e.g. Java.

Here is what I have so far:

Employee.java:

public class Employee {

    private String name = null;

    public Employee() {
        name = "Noob";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

ProgrammingLanguages.java:

public enum ProgrammingLanguages {
    JAVA,
    JAVASCRIPT,
    C,
    PHP,
    SWIFT,
    PYTHON
}

ProjectManager.java:

import java.util.ArrayList;
import java.util.List;

public class ProjectManager extends Employee {

private List<Employee> employeeList = null;

public ProjectManager() {
    employeeList = new ArrayList<Employee>();
}

public List<Employee> getEmployees() {
    return employeeList;
}

public void setEmployees(List<Employee> employees) {
    employeeList = employees;
}

public int getTotalWorkload() {
    int workload = 0;
    for (Employee employee : employeeList) {
        workload += employee.getWorkload(); // Error! Cannot resolve method 'getWorkload()'
    }
    return workload;
}
}

Developer:

import java.util.ArrayList;
import java.util.List;

public class Developer extends Employee {

private List<ProgrammingLanguages> languagesList = null;

private int workload = 0;

public Developer() {
    languagesList = new ArrayList<ProgrammingLanguages>();
}

public void setLanguagesList(List<ProgrammingLanguages> languagesList) {
    this.languagesList = languagesList;
}

public void addProgrammingLanguage(ProgrammingLanguages language) {
    languagesList.add(language);
}

public List<ProgrammingLanguages> getLanguagesList() {
    return languagesList;
}

public void setWorkload(int workload) {
    this.workload = workload;
}

public int getWorkload() {
    return workload;
}

}

Unfortunately, I get a compiler error in my ProjectManager class, any idea why?

Thanks in advance.

Upvotes: 3

Views: 1006

Answers (3)

Fuhrmanator
Fuhrmanator

Reputation: 12882

I am not a hundred percent sure about this design pattern, but I think that it is the perfect use case for this scenario, isn't it?

The GoF structure of Composite is as follows:

GoF Composite pattern

As you can see, Operation() is common in all elements. That would be your scenario's getWorkload() method.

However, it's somewhat inconsistent with the pattern in that it implies that a Manager has a workload that is composed of her employees. It's the contrary in real life, at least with a good manager. I would suggest changing the method name to something like getEffortUnderMyResponsibility(), to imply a responsibility for getting the work done, rather than actually doing the work. For programmers, it's true they actually do it; for managers, they are responsible for it getting done.

Upvotes: 1

Matthias A. Eckhart
Matthias A. Eckhart

Reputation: 5156

Yes, the composite pattern is indeed the right choice if you want to map tree structures. With reference to your example, the composite design pattern implies that your class Employee acts as a node, the class ProjectManager acts as a branch and the class Developer acts as a leaf. Within this context, the main advantage of the composite pattern is that it treats objects of your compositions uniformly. As a result, you can represent entire hierarchies of instances with this particular GoF design pattern.

You need the following participants:

  1. The abstract class Employee must declare the interface of the composition and implements a common behaviour to a certain degree.
  2. The ProjectManager class extends the abstract class Employee and implements a behaviour to treat Employee children, i.d. in your case ProjectManager or Developer instances.
  3. The Developer also extends the abstract class Employee and represents a leaf which does not have any children.

I used your example code to demonstrate the composite pattern. Please note that it may vary from your desired outcome, but you can take it as a reference.

Employee.java (node):

package me.eckhart;

import java.util.List;

public abstract class Employee {

    private String name = null;
    public static final String OPERATION_NOT_SUPPORTED = "Operation not supported.";

    public String getName() {
        return name;
    }

    public Employee setName(String name) {
        if (name == null) throw new IllegalArgumentException("Argument 'name' is null.");
        this.name = name;
        return this;
    }

    public Employee addEmployee(Employee employee) {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    public List<Employee> getEmployees() {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    public Employee setEmployees(List<Employee> employees) {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    public Employee setLanguagesList(List<ProgrammingLanguages> languagesList) {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    public Employee addProgrammingLanguage(ProgrammingLanguages language) {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    public List<ProgrammingLanguages> getLanguagesList() {
        throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
    }

    /* Composite operations. */

    public abstract int getWorkload(ProgrammingLanguages language);

    public abstract Employee setWorkload(int workload);

}

ProjectManager.java (branch):

package me.eckhart;

import java.util.ArrayList;
import java.util.List;

public class ProjectManager extends Employee {

    private List<Employee> employeeList = null;

    public ProjectManager() {
        this.employeeList = new ArrayList<>();
    }

    @Override
    public Employee addEmployee(Employee employee) {
        if (employee == null) throw new IllegalArgumentException("Argument 'employee' is null.");
        this.employeeList.add(employee);
        return this;
    }

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

    @Override
    public Employee setEmployees(List<Employee> employeeList) {
        if (employeeList == null) throw new IllegalArgumentException("Argument 'employeeList' is null.");
        this.employeeList = employeeList;
        return this;
    }

    /* Composite operations. */

    public int getWorkload(ProgrammingLanguages language) {
        int workload = 0;
        for (Employee employee : employeeList) {
            workload += employee.getWorkload(language);
        }
        return workload;
    }

    public Employee setWorkload(int workload) {
        throw new UnsupportedOperationException(Employee.OPERATION_NOT_SUPPORTED);
    }

}

Developer.java (leaf):

package me.eckhart;

import java.util.ArrayList;
import java.util.List;

public class Developer extends Employee {

    private List<ProgrammingLanguages> languagesList = null;

    private int workload = 0;

    public Developer() {
        this.languagesList = new ArrayList<>();
    }

    @Override
    public Employee setLanguagesList(List<ProgrammingLanguages> languagesList) {
        this.languagesList = languagesList;
        return this;
    }

    @Override
    public Employee addProgrammingLanguage(ProgrammingLanguages language) {
        this.languagesList.add(language);
        return this;
    }

    @Override
    public List<ProgrammingLanguages> getLanguagesList() {
        return this.languagesList;
    }

    /* Composite operations. */

    public Employee setWorkload(int workload) {
        if (workload < -1) throw new IllegalArgumentException("Workload cannot be negative.");
        this.workload = workload;
        return this;
    }

    public int getWorkload(ProgrammingLanguages language) {
        if (this.languagesList.contains(language)) return workload;
        return 0;
    }

}

ProgrammingLanguages.java (enumeration):

package me.eckhart;

public enum ProgrammingLanguages {
    JAVA,
    JAVASCRIPT,
    C,
    PHP,
    SWIFT,
    PYTHON
}

I created a unit test to demonstrate how you can access the workload for one particular programming language.

EmployeeTest.java (JUnit 4.11):

package me.eckhart;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class EmployeeTest {

    protected Employee projectManagerIt;

    @Before
    public void setUp() throws Exception {

        Employee webDevSr = new Developer();
        webDevSr.setName("Jane").addProgrammingLanguage(ProgrammingLanguages.JAVASCRIPT).addProgrammingLanguage(ProgrammingLanguages.PYTHON).setWorkload(10);
        Employee webDevJr = new Developer();
        webDevJr.setName("Alex").addProgrammingLanguage(ProgrammingLanguages.PHP).setWorkload(15);
        Employee projectManagerWebDev = new ProjectManager();
        projectManagerWebDev.setName("James").addEmployee(webDevSr).addEmployee(webDevJr);

        Employee softwareDevSr = new Developer();
        softwareDevSr.setName("Martin").addProgrammingLanguage(ProgrammingLanguages.C).addProgrammingLanguage(ProgrammingLanguages.JAVA).setWorkload(35);
        Employee softwareDevJr = new Developer();
        softwareDevJr.setName("John").addProgrammingLanguage(ProgrammingLanguages.JAVA).setWorkload(30);
        Employee projectManagerBackend = new ProjectManager();
        projectManagerBackend.setName("Tom").addEmployee(softwareDevSr).addEmployee(softwareDevJr);

        Employee freelanceSoftwareDev = new Developer();
        freelanceSoftwareDev.setName("Marco").addProgrammingLanguage(ProgrammingLanguages.JAVA).addProgrammingLanguage(ProgrammingLanguages.PYTHON).addProgrammingLanguage(ProgrammingLanguages.C).setWorkload(25);
        Employee freelanceWebDev = new Developer();
        freelanceWebDev.setName("Claudio").addProgrammingLanguage(ProgrammingLanguages.SWIFT).addProgrammingLanguage(ProgrammingLanguages.JAVASCRIPT).addProgrammingLanguage(ProgrammingLanguages.PHP).setWorkload(10);
        Employee freelanceProjectManager = new ProjectManager();
        freelanceProjectManager.setName("Angie").addEmployee(freelanceSoftwareDev).addEmployee(freelanceWebDev);

        projectManagerIt = new ProjectManager();
        projectManagerIt.setName("Peter").addEmployee(projectManagerWebDev).addEmployee(projectManagerBackend).addEmployee(freelanceProjectManager);

    }

    @Test
    public void testComposite() throws Exception {

        Assert.assertEquals(90, projectManagerIt.getWorkload(ProgrammingLanguages.JAVA));
        Assert.assertEquals(20, projectManagerIt.getWorkload(ProgrammingLanguages.JAVASCRIPT));
        Assert.assertEquals(60, projectManagerIt.getWorkload(ProgrammingLanguages.C));
        Assert.assertEquals(25, projectManagerIt.getWorkload(ProgrammingLanguages.PHP));
        Assert.assertEquals(10, projectManagerIt.getWorkload(ProgrammingLanguages.SWIFT));
        Assert.assertEquals(35, projectManagerIt.getWorkload(ProgrammingLanguages.PYTHON));

    }
}

The UML class diagram looks like this:

UML_class_diagram_composite_design_pattern_GoF

The code in the setUp() method of EmployeeTest.java implements the following tree structure:

Tree_structure_Composite_Pattern

The main disadvantage of the composite design pattern is that you need to restrict certain operations with runtime-checks, since clients usually do not know whether they are dealing with a ProjectManager (branch) or a Developer (leaf) instance.

Upvotes: 0

Nir Alfasi
Nir Alfasi

Reputation: 53525

The method getWorkload() is not defined in class Employee and you're trying to access it.

In order to solve the compilation error you should add this method to Employee - I would add it as abstract in order to force any (new) sub-class to implement it.

BTW, that's not composition-pattern - that's inheritance. You can (and should) read more about it.

Upvotes: 0

Related Questions