Composite - Design Pattern

Composite - Design Pattern

Design Patterns: Elements of Reusable Object-Oriented Software

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

What is Composite Design Pattern?

"Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly."

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.

comp_1.png

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.

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.

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).

comp_2.png

Let's get into the coding part.

abstract class CatalogComponent {
    open fun add(catalogComponent: CatalogComponent) {
        throw UnsupportedOperationException("Cannot add item to catalog.")
    }

    open fun remove(catalogComponent: CatalogComponent) {
        throw UnsupportedOperationException("Cannot remove item from catalog.")
    }

    open val name: String
        get() {
            throw UnsupportedOperationException("Cannot return name.")
        }
    open val price: Double
        get() {
            throw UnsupportedOperationException("Cannot return price.")
        }

    open fun print() {
        throw UnsupportedOperationException("Cannot print.")
    }
}

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.

class Product(override val name: String, override val price: Double) : CatalogComponent() {

    override fun print() {
        println("Product name: $name Price: $price")
    }
}

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

class ProductCatalog(override val name: String) : CatalogComponent() {
    private val items = ArrayList<CatalogComponent>()

    override fun print() {
        for (comp in items) {
            comp.print()
        }
    }

    override fun add(catalogComponent: CatalogComponent) {
        items.add(catalogComponent)
    }

    override fun remove(catalogComponent: CatalogComponent) {
        items.remove(catalogComponent)
    }
}

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.

So, Product and ProductCatalog extend CatalogComponent. So, the relation here is an inheritance. ProductCatalog can contain instances of Product and occasionally other ProductCatalog , both of which are CatalogComponent. This relation is called aggregation.

Let's test it

/*Create primary products for main catalog*/
val mJeanProduct: CatalogComponent = Product("M: Jeans 32", 65.00)
val mTShirtProduct: CatalogComponent = Product("M: T Shirt 38", 45.00)

/*Create a composite product catalog and add female products to it*/
val newCatalog: CatalogComponent = ProductCatalog("Female Products")
val fJeans: CatalogComponent = Product("F: Jeans 32", 65.00)
val fTShirts: CatalogComponent = Product("F: T Shirt 38", 45.00)
newCatalog.add(fJeans)
newCatalog.add(fTShirts)

/*Create a composite product catalog and add kid products to it*/
val kidCatalog: CatalogComponent = ProductCatalog("Kids Products")
val kidShorts: CatalogComponent = Product("Return Gift", 23.00)
val kidPlayGears: CatalogComponent = Product("Summer Play Gear", 65.00)
kidCatalog.add(kidShorts)
kidCatalog.add(kidPlayGears)

/*Create primary catalog and add primary products and new catalogs to it*/
val mainCatalog: CatalogComponent = ProductCatalog("Primary Catalog")
mainCatalog.add(mJeanProduct)
mainCatalog.add(mTShirtProduct)
mainCatalog.add(newCatalog)
mainCatalog.add(kidCatalog)

/*Print out product/catalog information*/
mainCatalog.print()

Let's take a look at the full implementation in Kotlin

Follow Me on LinkedIn