Composite Design Pattern

“Once you stop dreaming, You start dying” — Bill Johnson

When we write code we think of everything as an object and One object can hold multiple objects like a chain inside the parent object. Every object can be treated as a single instance of the same type of object. The intent is like Compose the object into a tree structure to represent part-whole hierarchies. So we can get a tree structure and every node or leaf can be represented as a single object. When programmers write code for tree-structured data they often discriminate between a leaf node and a branch. This makes the code more complex and the solution to this problem is to write an interface that allows treating complex and primitive objects uniformly.

So, we can use this pattern when we have several tree structure objects and we want to get the specific object or node as an individual object. According to the book, the main intent is like,

“ Compose Objects into tree structures to represent part-whole hierarchies. Composite lets client treat individual objects and compositions of objects uniformly” — Design Pattern Book

The main concept is that we can manipulate a single instance of an object as we would manipulate the group of objects. This pattern has four properties,

Component: It is the interface for an object in the composition and for accessing and managing its child components.

Leaf: It represents the leaf object in the composition.

Composite: It stores child components and implements the child-related operations in the component interface.

Client: It manipulates the object in the compositions through the component interface.

The client uses the component class interface to interact with the objects in the composition structure. If the recipient is a leaf then the object is handled directly. If it is composite, then it forwards a request to its child component and performs the additional operation.

Example:
Think about a situation you are writing code for an e-commerce software where you are dealing with different gadgets. Suppose you have mobile phones, tablets, laptops, desktops, etc. You can make a tree of those objects and even you can categories them. Finally, you can get the individual object through the composite pattern. Let’s implement this example with code now.

// Component Interface to manipulating child components
public interface Gadget {
public void showGadgetDetails();
}
// Mobile Phone as a leaf
public class Mobile implements Gadget{
private String brand;
private String model;
private Double price;

public Mobile(String brand, String model, Double price) {
this.brand = brand;
this.model = model;
this.price = price;
}

public String getBrand() {
return brand;
}

public String getModel() {
return model;
}

public Double getPrice() {
return price;
}

@Override
public String toString() {
return "Mobile{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", price=" + price +
'}';
}

@Override
public void showGadgetDetails() {
System.out.println("Brand Name: "+ getBrand() + " Model: " + getModel() + " Price: " + getPrice());
}
}
// Tablet as a leaf
public class Tablet implements Gadget{
private String brand;
private String model;
private Double price;

public Tablet(String brand, String model, Double price) {
this.brand = brand;
this.model = model;
this.price = price;
}

public String getBrand() {
return brand;
}

public String getModel() {
return model;
}

public Double getPrice() {
return price;
}

@Override
public String toString() {
return "Tablet{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", price=" + price +
'}';
}

@Override
public void showGadgetDetails() {
System.out.println("Brand Name: "+ getBrand() + " Model: " + getModel() + " Price: " + getPrice());
}
}
// Our Composite Class
public class GadgetDirectory implements Gadget{
private List<Gadget> gadgetList;

public GadgetDirectory() {
gadgetList = new ArrayList<>();

}


@Override
public void showGadgetDetails() {
for (Gadget gadget: gadgetList) {
gadget.showGadgetDetails();
}
}

public void addGadget(Gadget gadget){
gadgetList.add(gadget);
}

}
// Client Code
public class CompositeClient {
public static void main(String[] args) {
Mobile mobile = new Mobile("Apple","Iphone 12 Pro",1300.00);
Mobile mobile1 = new Mobile("Samsung","S20 Altra",1400.00);

System.out.println("Mobile phone as an individual Object.");

GadgetDirectory phoneDirectory = new GadgetDirectory();
phoneDirectory.addGadget(mobile);
phoneDirectory.addGadget(mobile1);
phoneDirectory.showGadgetDetails();

Tablet tablet = new Tablet("Samsung","Model S5",700.00);
Tablet tablet1 = new Tablet("Samsung","Model 30",790.00);

System.out.println("Tablet as an individual Object.");

GadgetDirectory tabletDirectory = new GadgetDirectory();
tabletDirectory.addGadget(tablet);
tabletDirectory.addGadget(tablet1);
tabletDirectory.showGadgetDetails();

System.out.println("All the objects together.");

GadgetDirectory gadgetDirectory = new GadgetDirectory();
gadgetDirectory.addGadget(phoneDirectory);
gadgetDirectory.addGadget(tabletDirectory);
gadgetDirectory.showGadgetDetails();



}
}

SOLID Principles:

  1. Open Closed Principle, we can introduce a new element type into the application without breaking the existing details.
  2. It also supports the Interface segregation in our example, if you look insight you will find almost all the principles but some principles are obviously not visible in the UML diagram.

Relation with other Pattern:

  1. We can use the iterator pattern to traverse through the child components. that’s how we can make a compound pattern.
  2. Chain of Responsibility is often used in conjunction with Composite. In this case, when a leaf component gets a request, it may pass it through the chain of all of the parent components down to the root of the object tree.

Reference:

  1. https://refactoring.guru/design-patterns/composite
  2. https://www.geeksforgeeks.org/composite-design-pattern/
  3. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns: elements of reusable object-oriented software. Addison-Wesley Professional, 1995.

Software Engineer | High Integrity System Graduate @Frankfurt University of Applied Sciences