Composite Design Pattern in Java
Want to learn more about the composite design pattern in Java? Take a look at this tutorial to learn how to implement this design pattern in your project.
Join the DZone community and get the full member experience.
Join For FreeHere I am with another useful design pattern for you — the Composite Design Pattern. I will try to point out the key features to remember while implementing the composite pattern for you.
Composite Design Pattern
The Composite Design Pattern is meant to "compose objects into a tree structure to represent part-whole hierarchies. Composite Pattern lets clients treat individual objects and compositions of objects uniformly".
- The Composite Design patterns describe groups of objects that can be treated in the same way as a single instance of the same object type.
- The Composite pattern allows us to "compose" objects into tree structures to represent part-whole hierarchies.
- In addition, the Composite patterns also allow our clients to treat individual objects and compositions in the same way.
- The Composite patterns allow us to have a tree structure for each node that performs a task.
- In object-oriented programming, a Composite is an object designed as a composition of one-or-more similar objects, all exhibiting similar functionality. This is known as a “has-a” relationship between objects.
Below is the list of classes/objects used in the composite pattern, which has four :
- Component – Component is the interface (or abstract class) for the composition of the objects and methods for accessing/processing its child or node components. It also implements a default interface to define common functionalities/behaviors for all component classes.
- Leaf – The leaf class defines a concrete component class, which does not have any further composition. The leaf class implements the component interface. It performs the command/task at its end only.
- Composite – The composite class defines a concrete component class, which stores its child components. The composite class implements the component interface. It forwards the command/task to the composite objects it contains. It may also perform additional operations before and after forwarding the command/task.
- Client – The client class uses the component interface to interact/manipulate the objects in the composition (Leaf and Composite).
To better understand this, let's take a look at an example of employees working in an organization.
Steps
- We create an interface to define functionalities we like to perform as composite and leaf objects. Below is the code of the
Worker
interface, which has methods forassignWork()
andperformWork()
. TheWork
interface will act as a component of the composite pattern in the example.
package org.trishinfotech.composite;
public interface Worker {
void assignWork(Employee manager, Work work);
void performWork();
}
The Work Class is defined as below:
xxxxxxxxxx
package org.trishinfotech.composite;
import java.util.ArrayList;
import java.util.List;
public class Work {
private Calculator workType;
private List<String> work = new ArrayList<String>();
public Work(Calculator workType, List<String> work) {
super();
this.workType = workType;
this.work = work;
}
public Calculator getWorkType() {
return workType;
}
public void setWorkType(Calculator workType) {
this.workType = workType;
}
public List<String> getWork() {
return work;
}
public void setWork(List<String> work) {
this.work = work;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Work [workType=").append(workType).append(", work=").append(work).append("]");
return builder.toString();
}
}
To keep the example simple, Here I am dealing only with simple calculations like Factorial value and Palindrome and Armstrong check. Since Pandirome can be a string as well, I have kept work as String in theWork
class. Below class defines the algorithms/formulas to perform Factorial, Palindrome and Armstrong. Here's the code for Calculator enum:
xxxxxxxxxx
package org.trishinfotech.composite;
import java.math.BigInteger;
public enum Calculator {
FACTORIAL {
public String calculate(String value) {
String answer = "NA";
try {
long longValue = Long.parseLong(value);
BigInteger factorialValue = BigInteger.valueOf(1);
for (long i = 1; i <= longValue; i++) {
factorialValue = factorialValue.multiply(BigInteger.valueOf(i));
}
answer = factorialValue.toString();
} catch (NumberFormatException exp) {
System.out.println("Can't calculate factorial of " + value);
}
return answer;
}
},
PALINDROME {
public String calculate(String value) {
String answer = "false";
if (value != null && !value.trim().isEmpty()) {
String reverse = (new StringBuilder(value).reverse().toString());
answer = Boolean.toString(reverse.equals(value));
}
return answer;
}
},
ARMSTRONG {
public String calculate(String value) {
String answer = "false";
try {
long longValue = Long.parseLong(value);
long number = longValue;
long armstrongValue = 0;
while (number != 0) {
long temp = number % 10;
armstrongValue = armstrongValue + temp * temp * temp;
number /= 10;
}
answer = Boolean.toString(String.valueOf(armstrongValue).equals(value));
} catch (NumberFormatException exp) {
System.out.println("Can't calculate armstrong of " + value);
}
return answer;
}
};
public abstract String calculate(String value);
}
I have created these algorithms as enums. To read more on Java-Enums, please refer to my article (Java Enums: How to Make Enums More Useful).
- We will create an abstract class of
Employee
to carry the common code for all various concrete subclasses of the employees.
xxxxxxxxxx
package org.trishinfotech.composite;
public abstract class Employee implements Worker {
protected long employeeId;
protected String employeeName;
protected String designation;
protected Department department;
public Employee(long employeeId, String employeeName, String designation, Department department) {
super();
this.employeeId = employeeId;
this.employeeName = employeeName;
this.designation = designation;
this.department = department;
}
public long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public abstract int teamSize();
public String fullDetails() {
StringBuilder builder = new StringBuilder();
builder.append("Employee [").append(employeeId).append(", ").append(employeeName).append(", ")
.append(designation).append(", ").append(department).append(", Team=").append(teamSize()).append("]");
return builder.toString();
}
public String shortDetails() {
StringBuilder builder = new StringBuilder();
builder.append("'").append(employeeName).append("'");
return builder.toString();
}
public String toString() {
return shortDetails();
}
}
The Employee
class declares abstract method teamSize() which I will use to split the work load assigned from a manager to anEngineer
. So teamSize() will will be 1 forEngineer
where as it will give the number of employees theManager
is managing.
TheEngineer
class is as below:
xxxxxxxxxx
package org.trishinfotech.composite;
import java.util.ArrayList;
import java.util.List;
public class Engineer extends Employee {
private List<Work> works = new ArrayList<Work>();
public Engineer(long employeeId, String employeeName, String designation, Department department) {
super(employeeId, employeeName, designation, department);
}
public int teamSize() {
return 1;
}
public void assignWork(Employee manager, Work work) {
this.works.add(work);
System.out.println(this + " has assigned work of '" + work + "' by manager " + manager);
}
public void performWork() {
System.out.println(this + " is performing work of '" + works + "'");
works.stream().forEach(work -> {
work.getWork().stream().forEach(value -> {
Calculator calculator = work.getWorkType();
System.out.println(this + " has result of work of '" + work + "' as : " + calculator.calculate(value));
});
});
works.clear();
}
}
- We will create the
Manager
class to use as the composite object, and we will have another Employee object as a collection via the composition.
The Manager
class is as below:
xxxxxxxxxx
package org.trishinfotech.composite;
import java.util.ArrayList;
import java.util.List;
public class Manager extends Employee {
List<Employee> managingEmployees = new ArrayList<Employee>();
public Manager(long employeeId, String employeeName, String designation, Department department) {
super(employeeId, employeeName, designation, department);
}
public boolean manages(Employee employee) {
return managingEmployees.add(employee);
}
public boolean stopManaging(Employee employee) {
return managingEmployees.remove(employee);
}
public int teamSize() {
return managingEmployees.stream().mapToInt(employee -> employee.teamSize()).sum();
}
public void assignWork(Employee manager, Work work) {
System.out.println(this + " has assigned work of '" + work + "' by manager " + manager);
System.out.println();
System.out.println(this + " distributing work '" + work + "' to managing-employees..");
int fromIndex = 0;
int toIndex = 0;
int totalWork = work.getWork().size();
List<String> assignWork = null;
while (toIndex < totalWork) {
for (Employee employee : managingEmployees) {
System.out.println("Assigning to " + employee);
int size = employee.teamSize();
toIndex = fromIndex + size;
assignWork = work.getWork().subList(fromIndex, toIndex);
if (assignWork.isEmpty()) {
return;
}
employee.assignWork(this, new Work(work.getWorkType(), assignWork));
fromIndex = toIndex;
}
break;
}
}
public void performWork() {
System.out.println(this + " is asking his managing employees to perform assigned work");
System.out.println();
managingEmployees.stream().forEach(employee -> employee.performWork());
System.out.println();
System.out.println(this + " has completed assigned work with the help of his managing employees");
System.out.println();
}
}
- We define Work in the property file instead of hard-coding into the
Main
class. Thework.properties
is defined as below:
xxxxxxxxxx
Calculate.Palindrome=1234321, 12341234, ABCDEDCBA, 4567887654, XYZZYX, 45676543, 3456543
Calculate.Armstrong=153, 8208, 2104, 4210818, 345321, 32164049651, 876412347
Calculate.Factorial=20, 43, 15, 120, 543, 35, 456, 432, 350, 44, 26, 17, 8
- The Main class uses
WorkLoader
class to load work from thework.properties
. TheWorkLoader
class is defined as below:
xxxxxxxxxx
package org.trishinfotech.composite;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
public class WorkLoader {
protected Properties properties = new Properties();
public WorkLoader(String fileName) {
try (InputStream input = new FileInputStream(fileName)) {
// load a properties file
properties.load(input);
} catch (IOException exp) {
exp.printStackTrace();
}
}
public Properties getProperties() {
return properties;
}
public List<Work> getWorkList() {
List<Work> workList = new ArrayList<Work>();
Set<Object> keys = properties.keySet();
for (Object key : keys) {
String workType = key.toString().substring("Calculate".length() + 1).toUpperCase();
String values = properties.getProperty(key.toString());
Work work = new Work(Calculator.valueOf(workType), Arrays.asList(values.split(", ")));
workList.add(work);
}
return workList;
}
}
- In the end, we will write the
Main
class as theClient
to execute and test our composite pattern code.
xxxxxxxxxx
package org.trishinfotech.composite;
public class Main {
public static void main(String[] args) {
Engineer ajay = new Engineer(1001l, "Ajay", "Developer", Department.ENG);
Engineer vijay = new Engineer(1002l, "Vijay", "SR. Developer", Department.ENG);
Engineer jay = new Engineer(1003l, "Jay", "Lead", Department.ENG);
Engineer martin = new Engineer(1004l, "Martin", "QA", Department.ENG);
Manager kim = new Manager(1005l, "Kim", "Manager", Department.ENG);
Engineer anders = new Engineer(1006l, "Andersen", "Developer", Department.ENG);
Manager niels = new Manager(1007l, "Niels", "Sr. Manager", Department.ENG);
Engineer robert = new Engineer(1008l, "Robert", "Developer", Department.ENG);
Manager rachelle = new Manager(1009l, "Rachelle", "Product Manager", Department.ENG);
Engineer shailesh = new Engineer(1010l, "Shailesh", "Engineer", Department.ENG);
kim.manages(ajay);
kim.manages(martin);
kim.manages(vijay);
niels.manages(jay);
niels.manages(anders);
niels.manages(shailesh);
rachelle.manages(kim);
rachelle.manages(robert);
rachelle.manages(niels);
WorkLoader workLoad = new WorkLoader("work.properties");
workLoad.getWorkList().stream().forEach(work -> {
rachelle.assignWork(rachelle, work);
});
rachelle.performWork();
}
}
- Below is the output of the program:
xxxxxxxxxx
'Rachelle' has assigned work of 'Work [workType=FACTORIAL, work=[20, 43, 15, 120, 543, 35, 456, 432, 350, 44, 26, 17, 8]]' by manager 'Rachelle'
'Rachelle' distributing work 'Work [workType=FACTORIAL, work=[20, 43, 15, 120, 543, 35, 456, 432, 350, 44, 26, 17, 8]]' to managing-employees..
Assigning to 'Kim'
'Kim' has assigned work of 'Work [workType=FACTORIAL, work=[20, 43, 15]]' by manager 'Rachelle'
'Kim' distributing work 'Work [workType=FACTORIAL, work=[20, 43, 15]]' to managing-employees..
Assigning to 'Ajay'
'Ajay' has assigned work of 'Work [workType=FACTORIAL, work=[20]]' by manager 'Kim'
Assigning to 'Martin'
'Martin' has assigned work of 'Work [workType=FACTORIAL, work=[43]]' by manager 'Kim'
Assigning to 'Vijay'
'Vijay' has assigned work of 'Work [workType=FACTORIAL, work=[15]]' by manager 'Kim'
Assigning to 'Robert'
'Robert' has assigned work of 'Work [workType=FACTORIAL, work=[120]]' by manager 'Rachelle'
Assigning to 'Niels'
'Niels' has assigned work of 'Work [workType=FACTORIAL, work=[543, 35, 456]]' by manager 'Rachelle'
'Niels' distributing work 'Work [workType=FACTORIAL, work=[543, 35, 456]]' to managing-employees..
Assigning to 'Jay'
'Jay' has assigned work of 'Work [workType=FACTORIAL, work=[543]]' by manager 'Niels'
Assigning to 'Andersen'
'Andersen' has assigned work of 'Work [workType=FACTORIAL, work=[35]]' by manager 'Niels'
Assigning to 'Shailesh'
'Shailesh' has assigned work of 'Work [workType=FACTORIAL, work=[456]]' by manager 'Niels'
'Rachelle' has assigned work of 'Work [workType=PALINDROME, work=[1234321, 12341234, ABCDEDCBA, 4567887654, XYZZYX, 45676543, 3456543]]' by manager 'Rachelle'
'Rachelle' distributing work 'Work [workType=PALINDROME, work=[1234321, 12341234, ABCDEDCBA, 4567887654, XYZZYX, 45676543, 3456543]]' to managing-employees..
Assigning to 'Kim'
'Kim' has assigned work of 'Work [workType=PALINDROME, work=[1234321, 12341234, ABCDEDCBA]]' by manager 'Rachelle'
'Kim' distributing work 'Work [workType=PALINDROME, work=[1234321, 12341234, ABCDEDCBA]]' to managing-employees..
Assigning to 'Ajay'
'Ajay' has assigned work of 'Work [workType=PALINDROME, work=[1234321]]' by manager 'Kim'
Assigning to 'Martin'
'Martin' has assigned work of 'Work [workType=PALINDROME, work=[12341234]]' by manager 'Kim'
Assigning to 'Vijay'
'Vijay' has assigned work of 'Work [workType=PALINDROME, work=[ABCDEDCBA]]' by manager 'Kim'
Assigning to 'Robert'
'Robert' has assigned work of 'Work [workType=PALINDROME, work=[4567887654]]' by manager 'Rachelle'
Assigning to 'Niels'
'Niels' has assigned work of 'Work [workType=PALINDROME, work=[XYZZYX, 45676543, 3456543]]' by manager 'Rachelle'
'Niels' distributing work 'Work [workType=PALINDROME, work=[XYZZYX, 45676543, 3456543]]' to managing-employees..
Assigning to 'Jay'
'Jay' has assigned work of 'Work [workType=PALINDROME, work=[XYZZYX]]' by manager 'Niels'
Assigning to 'Andersen'
'Andersen' has assigned work of 'Work [workType=PALINDROME, work=[45676543]]' by manager 'Niels'
Assigning to 'Shailesh'
'Shailesh' has assigned work of 'Work [workType=PALINDROME, work=[3456543]]' by manager 'Niels'
'Rachelle' has assigned work of 'Work [workType=ARMSTRONG, work=[153, 8208, 2104, 4210818, 345321, 32164049651, 876412347]]' by manager 'Rachelle'
'Rachelle' distributing work 'Work [workType=ARMSTRONG, work=[153, 8208, 2104, 4210818, 345321, 32164049651, 876412347]]' to managing-employees..
Assigning to 'Kim'
'Kim' has assigned work of 'Work [workType=ARMSTRONG, work=[153, 8208, 2104]]' by manager 'Rachelle'
'Kim' distributing work 'Work [workType=ARMSTRONG, work=[153, 8208, 2104]]' to managing-employees..
Assigning to 'Ajay'
'Ajay' has assigned work of 'Work [workType=ARMSTRONG, work=[153]]' by manager 'Kim'
Assigning to 'Martin'
'Martin' has assigned work of 'Work [workType=ARMSTRONG, work=[8208]]' by manager 'Kim'
Assigning to 'Vijay'
'Vijay' has assigned work of 'Work [workType=ARMSTRONG, work=[2104]]' by manager 'Kim'
Assigning to 'Robert'
'Robert' has assigned work of 'Work [workType=ARMSTRONG, work=[4210818]]' by manager 'Rachelle'
Assigning to 'Niels'
'Niels' has assigned work of 'Work [workType=ARMSTRONG, work=[345321, 32164049651, 876412347]]' by manager 'Rachelle'
'Niels' distributing work 'Work [workType=ARMSTRONG, work=[345321, 32164049651, 876412347]]' to managing-employees..
Assigning to 'Jay'
'Jay' has assigned work of 'Work [workType=ARMSTRONG, work=[345321]]' by manager 'Niels'
Assigning to 'Andersen'
'Andersen' has assigned work of 'Work [workType=ARMSTRONG, work=[32164049651]]' by manager 'Niels'
Assigning to 'Shailesh'
'Shailesh' has assigned work of 'Work [workType=ARMSTRONG, work=[876412347]]' by manager 'Niels'
'Rachelle' is asking his managing employees to perform assigned work
'Kim' is asking his managing employees to perform assigned work
'Ajay' is performing work of '[Work [workType=FACTORIAL, work=[20]], Work [workType=PALINDROME, work=[1234321]], Work [workType=ARMSTRONG, work=[153]]]'
'Ajay' has result of work of 'Work [workType=FACTORIAL, work=[20]]' as : 2432902008176640000
'Ajay' has result of work of 'Work [workType=PALINDROME, work=[1234321]]' as : true
'Ajay' has result of work of 'Work [workType=ARMSTRONG, work=[153]]' as : true
'Martin' is performing work of '[Work [workType=FACTORIAL, work=[43]], Work [workType=PALINDROME, work=[12341234]], Work [workType=ARMSTRONG, work=[8208]]]'
'Martin' has result of work of 'Work [workType=FACTORIAL, work=[43]]' as : 60415263063373835637355132068513997507264512000000000
'Martin' has result of work of 'Work [workType=PALINDROME, work=[12341234]]' as : false
'Martin' has result of work of 'Work [workType=ARMSTRONG, work=[8208]]' as : false
'Vijay' is performing work of '[Work [workType=FACTORIAL, work=[15]], Work [workType=PALINDROME, work=[ABCDEDCBA]], Work [workType=ARMSTRONG, work=[2104]]]'
'Vijay' has result of work of 'Work [workType=FACTORIAL, work=[15]]' as : 1307674368000
'Vijay' has result of work of 'Work [workType=PALINDROME, work=[ABCDEDCBA]]' as : true
'Vijay' has result of work of 'Work [workType=ARMSTRONG, work=[2104]]' as : false
'Kim' has completed assigned work with the help of his managing employees
'Robert' is performing work of '[Work [workType=FACTORIAL, work=[120]], Work [workType=PALINDROME, work=[4567887654]], Work [workType=ARMSTRONG, work=[4210818]]]'
'Robert' has result of work of 'Work [workType=FACTORIAL, work=[120]]' as : 6689502913449127057588118054090372586752746333138029810295671352301633557244962989366874165271984981308157637893214090552534408589408121859898481114389650005964960521256960000000000000000000000000000
'Robert' has result of work of 'Work [workType=PALINDROME, work=[4567887654]]' as : true
'Robert' has result of work of 'Work [workType=ARMSTRONG, work=[4210818]]' as : false
'Niels' is asking his managing employees to perform assigned work
'Jay' is performing work of '[Work [workType=FACTORIAL, work=[543]], Work [workType=PALINDROME, work=[XYZZYX]], Work [workType=ARMSTRONG, work=[345321]]]'
'Jay' has result of work of 'Work [workType=FACTORIAL, work=[543]]' as : 872891556790260456843586366934462570217404467584986862157951660218033476207283552939973350537069217537549478114229971312262991181125700413163606116971183695586311579361173915852193241844683585564114537080099157013082576149573523335369738442355599816710804068865355684599942047453968407440725166640261015140054933542126214585902983116193907129111747665196041855032660956095883694974186998924247749529335931094092429725640658310042199668214202637924631140057800119930627800378541111012271243379033822559451085315416955672912791395237932595257653475479021208979154389591618595449855779925751308303314870067464075830210893226767964127916083986194604372529850059441599156750579670067110415997949767860963439005485252514342425180564330056392659552281473502353159284918610493411467800558076001750687023376509841526414457026571388047691226042613278047076078395826335028468047405871941546558134196554638340479908186429178241794558954878506078646531853505428187507441610683354213870320835243780540993795316813622383436151335642255741817696689682127415809984693167490611336830354021351607247261703154384913620088006020923947745280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
'Jay' has result of work of 'Work [workType=PALINDROME, work=[XYZZYX]]' as : true
'Jay' has result of work of 'Work [workType=ARMSTRONG, work=[345321]]' as : false
'Andersen' is performing work of '[Work [workType=FACTORIAL, work=[35]], Work [workType=PALINDROME, work=[45676543]], Work [workType=ARMSTRONG, work=[32164049651]]]'
'Andersen' has result of work of 'Work [workType=FACTORIAL, work=[35]]' as : 10333147966386144929666651337523200000000
'Andersen' has result of work of 'Work [workType=PALINDROME, work=[45676543]]' as : false
'Andersen' has result of work of 'Work [workType=ARMSTRONG, work=[32164049651]]' as : false
'Shailesh' is performing work of '[Work [workType=FACTORIAL, work=[456]], Work [workType=PALINDROME, work=[3456543]], Work [workType=ARMSTRONG, work=[876412347]]]'
'Shailesh' has result of work of 'Work [workType=FACTORIAL, work=[456]]' as : 150777392777717065903328562798297482932764849966301315324902295697797980802999492049275470580840593582700556154654997912467653672836190567363944536581444396786039028419417159553169852939652733499484374432647121409002713034716885273557660568294514238651304204026421026217797122437474581042706674997505548774529387552185264469304745879944335896334980134727576771262477699704913814778801164976379963316514713032786305083016847394455111607701177156363125206697642497352441989049637406799105387152093299654856194446887474831405921359722324720996553956200165400519069670468845686118517860926559421327845227712982865242890852011587912148558934925229259778865164753102371910801614732061965104129730561590839408147446252948841011789641706225763887234100676084552005497753764496546383864694159909979495432469993306110242973486330432796522331628915418533758582252153753291412897349335363154308911927972242304805109760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
'Shailesh' has result of work of 'Work [workType=PALINDROME, work=[3456543]]' as : true
'Shailesh' has result of work of 'Work [workType=ARMSTRONG, work=[876412347]]' as : false
'Niels' has completed assigned work with the help of his managing employees
'Rachelle' has completed assigned work with the help of his managing employees
Here, I haven't use concurrency to keep the example simple. Please feel free to enhance the example as per your choice at your end.
Source Code can be found here: Composite-Design-Pattern-Sample-Code
Liked this article? Don't forget to press that like button. Happy coding!
Need more articles on design patterns? Check out these useful posts:
- Null Object Pattern in Java
- Using the Adapter Design Pattern in Java
- Using the Bridge Design Pattern in Java
- Strategy vs. Factory Design Patterns in Java
- Decorator Design Pattern in Java
- How to Use Singleton Design Pattern in Java
- Singleton Design Pattern: Making Singleton More Effective in Java
- Java Enums: How to Make Enums More Useful
- Java Enums: How to Use Configurable Sorting Fields
Opinions expressed by DZone contributors are their own.
Comments