Introduction
Objects within Objects
Any problem that can be designed as a tree structure will fall under the composite design pattern.
Example 1: Consider the example of a company structure. The CEO is at the top, followed by the Director, Manager, Marketing team, and IT Engineer. The IT Engineer is the leaf node with no one under this position.
Example 2: Take the example of a delivery box. The box can contain a product, or the product can be wrapped inside another box. This sequence can continue based on the product.
Standard UML Diagram - Composite Design pattern
Problem Statement - Design File System
In a file system, there are two types of objects: files and directories. A directory can have multiple files and directories, but a file is a single object that cannot contain any other objects.
The directory has a File object
File class
package ProblemStatement;
public class File {
private String fileName;
public File(String fileName) {
this.fileName = fileName;
}
public void ls() {
System.out.println("File Name "+fileName);
}
}
Directory Class
package ProblemStatement;
import java.util.ArrayList;
import java.util.List;
public class Directory {
private String directoryName;
private List<Object> objectList;
public Directory(String directoryName) {
this.directoryName = directoryName;
this.objectList = new ArrayList<>();
}
public void add(Object object){
objectList.add(object);
}
public void ls(){
System.out.println("Directory Name: "+directoryName);
for (Object obj: objectList){
if (obj instanceof File){
((File) obj).ls();
} else if (obj instanceof Directory) {
((Directory) obj).ls();
}
}
}
}
We can see from the code in the directory that, in order to print the list of files (i.e., ls()
), we need to always check the instances of the object. To avoid this code smell, we use the composite design pattern.
Solution
We can see that a directory contains files and other directories. Hence, we can use the composite design pattern.
So, we create an interface of a FileSystem and later create two child classes that implement this interface.
<<FileSystem>>
package Solution;
public interface FileSystem {
public void ls();
}
File Class
package Solution;
public class File implements FileSystem{
private String fileName;
public File(String fileName) {
this.fileName = fileName;
}
@Override
public void ls() {
System.out.println("File Name "+ fileName);
}
}
Directory Class
package Solution;
import java.util.ArrayList;
import java.util.List;
public class Directory implements FileSystem{
private String directoryName;
private List<FileSystem> fileSystemList;
public Directory(String directoryName) {
this.directoryName = directoryName;
this.fileSystemList = new ArrayList<>();
}
public void add(FileSystem fileSystemObj){
fileSystemList.add(fileSystemObj);
}
@Override
public void ls() {
System.out.println("Directory Name "+ directoryName);
for (FileSystem fileSystemObj: fileSystemList){
fileSystemObj.ls();
}
}
}
In the class above, we can see that there is no checking of instances of the object.
Driver class
package Solution;
public class Main {
public static void main(String[] args) {
Directory movieDirectory = new Directory("Movie");
FileSystem border = new File("Border");
movieDirectory.add(border);
Directory comedyMovieDirectory = new Directory("comedyMovie");
File hulchul = new File("hulchul");
comedyMovieDirectory.add(hulchul);
movieDirectory.add(comedyMovieDirectory);
movieDirectory.ls();
}
}
Problem Statment - Calculator
Arithmetic Expression Evaluator
Expression - 2 * ( 1 + 7)
Solution
The UML diagram represents the Arithmetic Expression. We will create an interface named ArithmeticExpression, which includes the evaluate method. We will have two child classes: one is 'Number,' and the other is 'Expression.' For the 'Number' class, the evaluate method returns the number itself. The 'Expression' class contains all the details about the expression, such as the arithmetic expressions on the left and right, and the type of operation. In the 'Expression' class, the evaluation method calculates the value based on the operation using a switch case.
UML
ArithmeticExpression class
package ExpressionEvaluation;
public interface ArithmeticExpression {
public int evaluate();
}
Number Class
package ExpressionEvaluation;
public class Number implements ArithmeticExpression{
private int value;
public Number(int value) {
this.value = value;
}
@Override
public int evaluate() {
System.out.println("Number Value is " + value);
return value;
}
}
Operation enum class
package ExpressionEvaluation;
public enum Operation {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE;
}
Expression class
package ExpressionEvaluation;
public class Expression implements ArithmeticExpression{
private Operation operation;
private ArithmeticExpression leftExpression;
private ArithmeticExpression rightExpression;
public Expression(Operation operation, ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) {
this.operation = operation;
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
@Override
public int evaluate() {
int value = 0;
switch (operation){
case ADD -> {
value += rightExpression.evaluate() + leftExpression.evaluate();
}
case SUBTRACT -> {
value += rightExpression.evaluate() - leftExpression.evaluate();
}
case DIVIDE -> {
value += rightExpression.evaluate() / leftExpression.evaluate();
}
case MULTIPLY -> {
value += rightExpression.evaluate() * leftExpression.evaluate();
}
}
System.out.println("Expression value is "+value);
return value;
}
}
Main Driver class
package ExpressionEvaluation;
public class Main {
public static void main(String[] args) {
ArithmeticExpression one = new Number(1);
ArithmeticExpression two = new Number(2);
ArithmeticExpression seven = new Number(7);
ArithmeticExpression addExpression = new Expression(Operation.ADD, one, seven);
ArithmeticExpression parentExpression = new Expression(Operation.MULTIPLY, two, addExpression);
System.out.println(parentExpression.evaluate());
}
}