Composite Design Pattern

The composite pattern is like our file system.

The system allows us to re-use the simple base products like the file and the folder, to build much more complex folder structures.

The beauty is that irrespective of the composition of a folder it remains as simple as an empty folder for the user operations!

Composite Design Pattern- Makes the whole and it’s part look similar

Its a structural design pattern that lets us build composite products in such way that it remains as simple as it’s base products. In other words, both the composite and the base products share a uniform interface with it’s clients.

Instead of exposing the complexity, the pattern handles it on its own using a whole and it’s part relation using a tree structure. More specifically, it works on the concept that a whole is a sum of its parts. Hence, the user operations on the whole is nothing but a combination of similar operations on all its underlying parts.

For example, just think of an user trying to delete a composite folder as shown above. Even though it needs the deletion of the underlying folder structure, the system handles this for the user. Thereby, it makes a composite folder behave as simple as a single folder for the user.

Sample Usages of the Pattern

This pattern greatly simplifies our design process because it allows us to build complex objects using simpler components. Secondly, it also allows us to organize large number of items in structured hierarchical tree. Here, are some more examples to showcase its usefulness.

  • Working on Diagrams : A diagram may consists of multiple other diagrams and basic shapes.
    • When we have to draw the main diagram, a composite pattern helps us draw the underlying diagrams automatically.
    • When we move, copy , re-size a group; we just do it on the group as a single object. But, the group knows its constituents and applies the same operation without needing any intervention from the client.
  • Building Menu : A composite structure can help us organize and display menu items in a hierarchical structure. Here, we are grouping multiple menu items into logically grouped menu items.
  • Building Flexible UI : Composing flexible UI using widgets, portlets or using reusable components as in React, Angular etc.
  • Files & Folder Browser : As we have discussed above, the user operates on the files and folders as individual items whereas the system takes care of applying the operations to the underlying composition.
  • Auto-start manager that starts a customizable list of apps that we want to start during our device startup.

 

How does the pattern work?

Note : Bill of Material(BOM) is like a recipe when we are preparing our food. It holds details on the parts and sub-parts that we need to build our products.

Lets say we are building a BOM module for a sports equipment manufacturer. Its a complex requirement to manage part details on so many products. Here, is the sample detail on a skate board product.

How to manage such a large list of products and their parts for so many different products ?

Lets say we got an order for 10000 skateboards. How to calculate the overall material requirement form this hierarchical structure. The composite pattern can easily solve this kind of whole and its part structures. It can convert the complex composite objects to look simple as our simple individual parts.

In this demo we will see how does it do it. Here, is the design to start with.

Composite Design Pattern

From the diagram we can see a composite product can include any combination of individual and composite products. Since it has all the details on its parts, it itself can implement whatever the key things the client may need from its parts. It is just similar to the folder deletion example at the top.

Source Code for Demo

For our demo lets see how the client can build the products and parts for our skateboard. Then, we will try to get the material required for the products at different levels.

package spectutz.dp.struct.composite;

import spectutz.dp.struct.composite.vo.BaseProductPart;
import spectutz.dp.struct.composite.vo.CompositeProductBOM;
import spectutz.dp.struct.composite.vo.ProductBOM;

public class ProductBOMClientDemo {
	public static void main(String[] args) {
		ProductBOM skateBoard = new CompositeProductBOM("SKATE_BOARD", 1);
		//Adding individual products to SKATE_BOARD
		skateBoard.addChild(new BaseProductPart("BOARD", 1));
		skateBoard.addChild(new BaseProductPart("AXIS", 1));
		skateBoard.addChild(new BaseProductPart("SCREW", 8));
		
		ProductBOM wheels = new CompositeProductBOM("WHEEL", 4);
		wheels.addChild(new BaseProductPart("TIRE", 1));
		wheels.addChild(new BaseProductPart("RIM", 1));
		wheels.addChild(new BaseProductPart("SCREW", 4));
		//Adding a composite product to SKATE_BOARD
		skateBoard.addChild(wheels);
		
		System.out.println("\nMaterial requirement for:");
		System.out.println("\nOne Board(individual) :\n" + new BaseProductPart("BOARD", 1).getMaterialRequirement());
		System.out.println("\nOne wheel(composite) :\n" + wheels.getMaterialRequirement());
		System.out.println("\nOne skateboard(composite) :\n" + skateBoard.getMaterialRequirement());
	}
}
// Output 
/* 
Material requirement for:

One Board(individual) :
{BOARD=1}

One wheel(composite) :
{SCREW=4, RIM=1, TIRE=1}

One skateboard(composite) :
{BOARD=1, SCREW=24, RIM=4, AXIS=1, TIRE=4}
*/

The comments in the demo code are self explanatory. But, the key thing to notice here is the flexibility of the design. It can accommodate any combination of the whole and part structure. Again, we can present a wide verity products using a common interface to our clients. Moreover, while getting the parts list the client is least bothered about the internal composition.

The source code below shows the implementation part.

package spectutz.dp.struct.composite.vo;

import java.util.Iterator;
import java.util.Map;

//Common interface for the base product and composite products
public abstract class ProductBOM {

	private String productName;
	private int units;

	public ProductBOM(String productName, int units) {
		this.productName = productName;
		this.units = units;
	}
	public String getProductName() {
		return productName;
	}
	public int getUnits() {
		return units;
	}
	
	public void addChild(ProductBOM productBOM) {
		throw new UnsupportedOperationException();
	}
	public void removeChild(ProductBOM productBOM) {
		throw new UnsupportedOperationException();
	}
	public Iterator<ProductBOM> getChildIterator() {
		throw new UnsupportedOperationException();
	}
	public Map<String,Integer> getMaterialRequirement() {
		throw new UnsupportedOperationException();
	}

}
package spectutz.dp.struct.composite.vo;
import java.util.HashMap;
import java.util.Map;

//This is for individual products
public class BaseProductPart extends ProductBOM{
	public BaseProductPart(String productName, int units) {
		super(productName, units);
	}
	
	//Simply returns the individual product
	public Map<String, Integer> getMaterialRequirement() {
		Map<String, Integer> materialRequirement = new HashMap<String, Integer>();		
		materialRequirement.put(this.getProductName(),1);		
		return materialRequirement;
	}
}
package spectutz.dp.struct.composite.vo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

//This is for composite products - We can flexibly add the part list
//The part list may include individual or composite products
public class CompositeProductBOM extends ProductBOM{
	protected List<ProductBOM> partList = new ArrayList<ProductBOM>();
	
	public CompositeProductBOM(String productName, int units) {
		super(productName, units);
	}

	@Override
	public void addChild(ProductBOM productBOM ) {
		this.partList.add(productBOM);		
	}
	@Override
	public void removeChild(ProductBOM productBOM ) {
		this.partList=this.partList.stream()
				.filter(part ->!part.getProductName().equalsIgnoreCase(productBOM.getProductName()))
				.collect(Collectors.toList());		
	}
	@Override
	public Iterator<ProductBOM> getChildIterator() {
		return partList.iterator();		
	}
	
	//Does the required calculation for its contained parts
	public Map<String,Integer> getMaterialRequirement() {
		Map<String, Integer> materialRequirement = new HashMap<String, Integer>();		
		Iterator<ProductBOM> iterator = partList.iterator();

		while(iterator.hasNext()) {
			ProductBOM part = iterator.next();
			int  quantity   = part.getUnits();
			Map<String, Integer> materialRequirementPerPart = part.getMaterialRequirement();
			
			materialRequirementPerPart.forEach((k,v)->{
				int requirement = v.intValue()*quantity;
			
				//Update part requirement into it's composing product
				if(materialRequirement.containsKey(k)) {
					int existingRequirement = materialRequirement.get(k);	
					materialRequirement.put(k, requirement+existingRequirement);					
				} else {					    
					materialRequirement.put(k, requirement);
				}
			});
		}
		return materialRequirement;
	}
}

The key thing to observer here is the difference between how the individual and composite return theirs parts list. While the first one is straight forward, the second one automates this for the end users using its part list.

Comparing with other patterns
  1. Decorators Vs Composition

Since both use composition of products these two patterns sounds similar. But, the difference lies in the change in the behavior of the composed parts.

In case of the decorators, the composition becomes a single product. All the layers together decide the unique behavior of the final product. For example, when we add the filter GZIPInputStream on top of FileInputStream, we can only read .gz deflated stream but, not a regular inflated file.

In contrast, the composition is a collection of parts where each part retains it’s own identity. The parts are simply grouped together to look like an assorted platter. For example, when a group of portlets forms a page, each portlet renders it’s own part.

Conclusion

This is quite an useful pattern with a wide range of usage as mentioned above. It allows us to flexibly group and compose new products based on the existing base products. The beauty of the composition being, the client need not bother about the complexity of the composition. It’s because the pattern guides us build composite products with similar interface to the base products.

In short, the pattern is an implementation of the divide and conquer rule. And, mostly using the concept – “The whole is the sum of its parts!