Design patterns: Composite – With Kotlin examples

In software and the real world, there are objects with different structures some of them are primitive or simple and some consist of other objects (Children) and many of these children consist of other objects and that is ok, but the problem appears when we need the same task to be done by the primitive and the container objects, in this case, the primitive object will give us a result directly but the container object needs to access its children and collect their results after checking their type, knowing that one child could be a container object !.

If the number of objects is small or container objects consist of few children the processes will be simpler but the complexity will increase by adding new object as a primitive, container or container’s child and here the composite pattern will be very useful because it organizes objects in tree or hierarchy structure whenever it is a primitive or container and then gives the ability to access all objects using one interface.

The following is composite’s intent as defines in Design patterns book by The Gang of Four :

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

Let’s illustrate more by taking a simple example, assume there is a  restaurant that offers different kinds of food some of them are single item(ex burger) and others are Combo-meal (ex burger and fries), to get its menu we need name and price of each item, the actual price in case of a single item or total for a combo, to apply composite pattern at this example we need to identify three participants: Component, Leaf, and Composite. 

Component (Menu item)

Defines the common operations of primitive and composite objects 

Leaf (Single menu item (burger, pizza .. etc))

Defines the behavior of primitive objects that have no children

Composite (Combo-meal)

Defines behavior for components having children including children management operations.

The following diagram illustrates the complete structure


 

Therefore a composite consists of leaves and sometimes another composites as the following diagram


 

Implementation:

Now let’s return back to our example and start to implement it by defining the component interface MenuItem

 

interface MenuItem {
    fun setName(itemName: String)
    fun getPrice(): Double
    fun printItem()
}

Meal class defines the behavior of leaves

 

class Meal(val mealPrice: Double) : MenuItem {
    var mealName: String = ""

    override fun setName(itemName: String) {
        mealName = itemName
    }

    override fun getPrice(): Double {
        return mealPrice
    }

    override fun printItem() {
        println("$mealName - $mealPrice")
    }
}

CompoMeal defines the behavior of composite items.

 

class CompoMeal : MenuItem {
    private var mealName = ""
    private val items = mutableListOf<MenuItem>()

    fun addItem(menuItem: MenuItem) {
        items.add(menuItem)
    }

    fun removeItem(menuItem: MenuItem) {
        items.remove(menuItem)
    }

    override fun setName(itemName: String) {
        mealName = itemName
    }

    override fun getPrice(): Double {
        var total = 0.0
        items.forEach {
            total += it.getPrice()
        }
        return total
    }

    override fun printItem() {
        println("$mealName - ${getPrice()}")
    }
}

 

 

fun main() {
    val menuItems = mutableListOf<MenuItem>()

    val burger = Meal(100.0).apply { setName("Burger") }
    val fries = Meal(50.0).apply { setName("Fries") }
    val pizza = Meal(200.0).apply { setName("Pizza") }
    val burgerCombo = CompoMeal().apply { setName("Burger combo meal") }
    val awesomeCombo = CompoMeal().apply { setName("Awesome combo meal") }

    burgerCombo.apply {
        addItem(burger)
        addItem(fries)
    }

    awesomeCombo.apply {
        addItem(burgerCombo)
        addItem(pizza)
    }

    menuItems.addAll(listOf(burger, pizza, burgerCombo, awesomeCombo))

    menuItems.forEach {
        it.printItem()
    }
}

 

Output:

Burger - 100
Pizza - 200
Burger combo meal - 150
Awesome combo meal - 350

 

In this example, the operations of child management are defined in Composite so the client may need to check if the component is composite or not before calling child operations. There is another method by defining them in the Component interface, in this case, the client does not need to check component type but on the other hand, leaf components will have extra methods without an actual need for them. Each of these solutions can be used but the better choice depends on the situation.

 The composite pattern provides a flexible structure to deal with complex objects and as a result the maintenance becomes easier. Sometimes it is hard to define an interface that contains common operations between leaves and composites so at the beginning analyze the whole system and make sure it can be reorganized into a hierarchical structure with a common interface.

 

 


Leave a Reply

Your email address will not be published. Required fields are marked *