Composite Pattern: Introduction

As a programmer you will deal with hierarchical trees of objects at some point or other. Hierarchical tree structures can come in different flavors, and one can be a tree of components (think as objects) that can be either leaf or node. A leaf is an object that doesn’t have children, while a node does. A node can have one or more leaves or other nodes. This is called recursive composition and can be best illustrated through a file system directory structure.

A challenge in creating such hierarchical tree structures is to provide clients a uniform way to access and manipulate objects of the tree. Clients should remain unaware whether any operation is being done on a leaf or a node, and this is where the composite design pattern comes in. As an example, composite design pattern can ensure that the process to add or delete a directory (node) and a file (leaf) remains the same for a user.

The composite pattern is part of the classic Gang of Four structural pattern group. Using this pattern, you can create hierarchical object trees in a uniform manner without going through complexities, such as object casting, type evaluations, and conditional checks to see if one object is independent or contains other objects.

Participants of the Composite Pattern

To understand how the composite pattern works, consider a shopping store that provides a catalog to help users browse products before buying. Initially, the shopping store had few products manufactured in-house. With expansion, gradually several products got added, some of which are purchased from other manufactures. For different categories of products, sub catalogs were created, and some sub catalogs further had their own sub catalogs. The requirement here is to organize the products and sub catalogs efficiently in a single main catalog. For such requirements, we have the composite pattern.

Let’s review what the GoF authors say about the composite pattern.

“Compose objects into tree structures to represent part-whole hierarchies”: A part-whole hierarchy is composed of smaller individual objects called Parts and larger objects called Wholes that are aggregation of Parts. What the pattern says is- for part-whole hierarchies, create tree structures to represent relationships between the Parts and Wholes.

“Composite lets clients treat individual objects and compositions of objects uniformly”: This means that a client should be able to apply the same operations over both aggregation of objects (Wholes) and individual objects (Parts).

To apply the composite pattern to the catalog example, we can create a tree structure (upside down) and model the root of the tree as an abstract class, CatalogComponent. This class defines the behavior of both individual and composite objects and acts as an interface of the tree to clients. Note that we could have also gone for a Java interface instead, but in the context of our example, that would force classes down the tree to provide implementation of methods not relevant to them. For example, a getProductDiscount() method for a product is not relevant to a catalog, and we don’t expect catalog to implement it.

Next, we need to create a class to model leaves, Product and one to model nodes, ProductCatalog.

Let’s look how our classes map to the participants of the composite pattern:

Component (CatalogComponent): An abstract base class for the objects in the tree structure. This class defines the default behavior for all objects and behaviors to access and manage child components in the tree.

Leaf(Product): Is a class that extends Component to represent leaves in the tree structure that does not have any children.

Composite (ProductCatalog): Is a class that extends Component to represent nodes (contain children) in the tree structure. This class stores Leaf components and implements the behaviors defined in Component to access and manage child components. As mentioned earlier, child components can be one or more Leaf or other Composite components.

Client: Interacts with Component to access and manipulate objects in the composition.

This is how the class hierarchy structure of the catalog example will be.

As you can see in the figure above, Product and ProductCatalog extend CatalogComponent. So, the relation here is inheritance. ProductCatalog can contain instances of Product and occasionally other ProductCatalog, both of which are CatalogComponent. This relation is called aggregation.

Applying the Composite Pattern

To apply the composite pattern to the catalog example, let’s write the abstract base class- the Component.

In the CatalogComponent class above, we wrote two types of methods and it’s important to identify and distinguish them:

In Line 6 – Line 12, we wrote the composite methodsadd() and remove() that adds and removes CatalogComponent objects respectively. Both the methods can add or remove product from a catalog, or a catalog from the parent catalog.

In Line 14 – Line 25, we wrote the operation methods: getName(), getPrice(), and print(). These methods apply to both Product and ProductCatalog. Although at first look, getPrice() might not seem relevant to ProductCatalog– but being good programmers, we know that we might later need to find the price of the products in a catalog.

In the CatalogComponent class above, notice that each method throws an exception of type UnsupportedOperationException. From the composite pattern design point of view, this is not mandatory. But, we did it because some methods will only be relevant to Product, while some to ProductCatalog. For the irrelevant methods, Product and ProductCatalog don’t need to do anything- just inherit them by default. If some code unknowingly calls them, the exception will get thrown. We will next write the Leaf of the tree hierarchy.

Product.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

packageguru.springframework.gof.composite;

publicclassProductextendsCatalogComponent{

privateStringname;

privatedoubleprice;

publicProduct(Stringname,doubleprice){

this.name=name;

this.price=price;

}

@Override

publicStringgetName(){

returnthis.name;

}

@Override

publicdoublegetPrice(){

returnthis.price;

}

@Override

publicvoidprint(){

System.out.println("Product name: "+name+" Price: "+price);

}

}

The Product class that we wrote above is simple. Being a leaf, we don’t have to worry about children. So, we only extended it from CatalogComponent and provided implementations of the operation methods: getName(), getPrice(), and print().

Next is the ProductCatalog class- the Composite and this is where we will implement both the operation and composite methods:

ProductCatalog.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

packageguru.springframework.gof.composite;

importjava.util.ArrayList;

publicclassProductCatalogextendsCatalogComponent{

privateArrayList<CatalogComponent>items=newArrayList<>();

privateStringname;

publicProductCatalog(Stringname){

this.name=name;

}

@Override

publicStringgetName(){

returnname;

}

@Override

publicvoidprint(){

for(CatalogComponent comp:items)

{

comp.print();

}

}

@Override

publicvoidadd(CatalogComponent catalogComponent){

items.add(catalogComponent);

}

@Override

publicvoidremove(CatalogComponent catalogComponent){

items.remove(catalogComponent);

}

}

In the ProductCatalog class above, we instantiated an ArrayList to hold CatalogComponent objects. We then implemented the following composite methods:

In Line 13 -Line 15, we wrote the getName() method to return the name of a CatalogComponent object.

In Line 17 – Line 23, we wrote the print() method and used a for-each loop to traverse the products in ArrayList and print out their information.

We also implemented the following composite methods:

In Line 26 – Line 28, we wrote the add() method to add a CatalogComponent object, passed to it, to the ArrayList.

In Line 31 – Line 33, we wrote the remove() method to remove a CatalogComponent object, passed to it, from the ArrayList.

Lets write some code using JUnit to test our implementation of the composite pattern in Java.

CatalogComponentTest

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

packageguru.springframework.gof.composite;

importorg.junit.Test;

importstaticorg.junit.Assert.*;

publicclassCatalogComponentTest{

@Test

publicvoidtestPrint()throwsException{

/*Create primary products for main catalog*/

CatalogComponent mJeanProduct=newProduct("M: Jeans 32",65.00);

CatalogComponent mTShirtProduct=newProduct("M: T Shirt 38",45.00);

/*Create a composite product catalog and add female products to it*/

CatalogComponent newCatalog=newProductCatalog("Female Products");

CatalogComponent fJeans=newProduct("F: Jeans 32",65.00);

CatalogComponent fTShirts=newProduct("F: T Shirt 38",45.00);

newCatalog.add(fJeans);

newCatalog.add(fTShirts);

/*Create a composite product catalog and add kid products to it*/

CatalogComponent kidCatalog=newProductCatalog("Kids Products");

CatalogComponent kidShorts=newProduct("Return Gift",23.00);

CatalogComponent kidPlayGears=newProduct("Summer Play Gear",65.00);

kidCatalog.add(kidShorts);

kidCatalog.add(kidPlayGears);

/*Create primary catalog and add primary products and new catalogs to it*/

CatalogComponent mainCatalog=newProductCatalog("Primary Catalog");

mainCatalog.add(mJeanProduct);

mainCatalog.add(mTShirtProduct);

mainCatalog.add(newCatalog);

mainCatalog.add(kidCatalog);

/*Print out product/catalog information*/

mainCatalog.print();

}

}

In the test class above, we instantiated and initialized some Product objects and added few of them to ProductCatalog objects. We then added independent Product and composite ProductCatalog objects, to a main catalog. Finally, we called print() to print out information of all products. In the code, notice that we called the add() method on both Product and ProductCatalog, which internally are different- Leaf vs Node. Doing so, we can say that our client can “treat individual objects and compositions of objects uniformly”.

Conclusion

The essence of composite pattern is the ability for a client to perform operations on an object without knowing if there are any nested objects. But like all good things, composite pattern too comes with a price. The pattern can make your design overly general. In the composite pattern, you have to define the composite methods at the root of the class hierarchy, and clients might mistakenly attempt to add or remove objects from leaf objects. But, keep in mind that design patterns do not come out of the box ready for any scenario. Design patterns are just a starting point to work from. You will need to adapt them to your requirements. That is what we exactly did by making the root component an abstract class and throwing exceptions in its composite methods.

When using the composite pattern during Enterprise Application Development with the Spring Framework, you might encounter exception of type BeanCreationException during constructor injection because of circular references. This is not Spring-specific and the problem can also arise in plain Java using regular constructors- Constructor invocations should be done in proper order. An alternative in Spring is to configure the components through setter injections to break any cyclic reference required during construction.