Reputation: 33
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
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:
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
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:
abstract
class Employee
must declare the interface of the composition and implements a common behaviour to a certain degree.ProjectManager
class extends the abstract
class Employee
and implements a behaviour to treat Employee
children, i.d. in your case ProjectManager
or Developer
instances.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:
The code in the setUp()
method of EmployeeTest.java
implements the following tree structure:
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
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