Java
I want to implement a composite pattern in Java
order to map a software development organization. So let's say there are multiple project managers and multiple developers. Each developer is assigned exactly one project manager, and each developer can program in different programming languages. Project managers lead developers and know exactly their workload.
I'm not one hundred percent sure of this design pattern, but I think it's the perfect use case for this scenario, isn't it?
The result should be as follows:
I want to request a project manager to check the workload of all developers who can code in a specific programming language for example. Java
...
Here's 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 am getting a compiler error in the class ProjectManager
, any idea why?
Thanks in advance.
source to share
Yes, the composite pattern is indeed the right choice if you want to map tree structures. As for your example, the composite design pattern assumes that your class Employee
acts as a node, the class ProjectManager
acts as a branch, and the class Developer
acts as a leaf. In this context, the main advantage of a composite painting is that it treats the objects in your compositions evenly. As a result, you can represent entire hierarchies of instances with this GoF design pattern.
You need the following members:
- The class
abstract
Employee
must declare a composition interface and implement common behavior to some extent. - The class
ProjectManager
extends the classabstract
Employee
and implements behavior to handleEmployee
children, id in your cases,ProjectManager
orDeveloper
. -
Developer
also extends the classabstract
Employee
and represents a sheet that has no children.
I used your example code to demonstrate a composite template. Please note that this may differ from your desired result, but you can consider it a link.
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
(sheet):
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 a workload for one specific 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:
The code in the method setUp()
EmployeeTest.java
implements the following tree structure:
The main disadvantage of the composite design pattern is that you need to restrict certain operations to runtime checks, since clients usually don't know if they are dealing with a ProjectManager
(branch) or Developer
(leaf) instance.
source to share
I'm not one hundred percent sure of this design pattern, but I think it's the perfect use case for this scenario, isn't it?
The GoF Composite structure looks like this:
As you can see, it Operation()
is common to all elements. This will be your script getWorkload()
.
However, it is somewhat inconsistent with the pattern as it implies that it Manager
has a workload of its employees. It's the other way around in real life, at least with a good manager. I would suggest changing the name of the method to something like getEffortUnderMyResponsibility()
to imply responsibility for doing the work, rather than actually doing that work. It's true for programmers that they actually do it; for managers, they are responsible for its implementation.
source to share
The method is getWorkload()
not defined in the Employee class and you are trying to access it.
To resolve the compilation error, you must add this method to Employee - I would add it as abstract
, to force any (new) subclass to execute it.
By the way, this is not a composition pattern - it is inheritance. You can (and should) read more about this.
source to share