Design patterns: Decorator – With Kotlin examples

The Decorator pattern (also known as Wrapper) is another pattern that categorized as a structural pattern and its intent:

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Let’s assume than we have a store that makes ice cream and offers serval topping such as honey and nuts. The store has three flavors Vanilla flavor, strawberry flavor, and chocolate flavor.

Customers can request lots of combinations (assuming no mixing between flavors) such as vanilla ice cream with honey topping, chocolate ice cream with honey and nuts topping, and more and more combinations.
If we extend the Ice cream class to create a concrete class for each combination, that will lead us to the class explosion.

A flexible solution to this problem is to enclose the ice cream in another object that can add the topping. The enclosing object is called a decorator. The decorator adds the topping and then forward the ice cream order to the ice cream component, but the decorator may perform its additional actions before or after forwarding. Then you can add another decorator – another topping- as much as the customer likes. So by doing this we attach additional responsibilities to the ice cream object dynamically – when the customer requests topping- without the need to subclassing the ice cream to cover all kinds of ice cream and topping combinations.

So if we want to have a vanilla ice cream with honey topping and nuts topping we can use honey topping decorator and nuts topping decorator to achieve that. So we simply compose the topping decorators with the vanilla ice cream to produce the desired results.

Honey topping decorator and nuts topping decorator classes are subclasses of topping decorator, which are an abstract class for ice cream to decorate it, and the ice cream itself is the abstract class for different flavors of ice cream.

Let’s take a look at the Decorator pattern structure and then come back to the ice cream store example.

The participants of the Decorator pattern the diagram above showed are:

  • Component (Ice cream)
    • Defines the interface for objects which responsibilities can be added to them dynamically.
  • ConcreateComponent (Vanilla Ice cream, Strawberry Ice cream, Chocolate Ice cream)
    • Defines an object to which additional responsibilities can be attached.
  • Decorator (Topping decorator)
    • The decorator maintains a reference to a Component object and defines an interface that conforms to Component’s interface.
  • ConcreateDecorator (Honey topping decorator, Nuts topping decorator)
    • Add responsibilities to the component.

The decorator forwards requests to its Component object, and it may perform additional operations before and after forwarding the request.

Based on the structure of the Decorator pattern, let’s decorate our ice cream. Let’s apply the decorator pattern to solve our ice cream store problem and add a couple of functions to it to make it a complete example.

As you can see the ToppingDecortor has the type IceCream as the Component which is the IceCream, also HoneyToppingDecorator and NutsToppingDecorator have a reference to the IceCream‘s object. Decorators have additional operations to be performed.

Here we have a complete working program based on the ice cream store UML after applying the decorator pattern.

The IceCream class is an abstract class with the abstract function getDescription(), so any other class that inherits it has to implement it.

abstract class IceCream {
    abstract fun getDescription(): String
}

class VanillaIceCream : IceCream() {
    override fun getDescription() = "Vanilla ice cream"
}

class StrawberryIceCream : IceCream() {
    override fun getDescription() = "Strawberry ice cream"
}

class ChocolateIceCream : IceCream() {
    override fun getDescription() = "Chocolate ice cream"
}

 

Here we have the decorator ToppingDecorator that inherits from IceCream.

abstract class ToppingDecorator() : IceCream() {
    abstract fun addTopping(): String
}

class HoneyToppingDecorator(private val iceCream: IceCream) : ToppingDecorator() {
    override fun getDescription() = iceCream.getDescription() + addTopping()
    override fun addTopping() = " With honey topping &"
}

class NutsToppingDecorator(private val iceCream: IceCream) : ToppingDecorator() {
    override fun getDescription() = iceCream.getDescription() + addTopping()
    override fun addTopping() = " With nuts topping &"
}

 

Here is the complete working example.

abstract class IceCream {
    abstract fun getDescription(): String
}

class VanillaIceCream : IceCream() {
    override fun getDescription() = "Vanilla ice cream"
}

class StrawberryIceCream : IceCream() {
    override fun getDescription() = "Strawberry ice cream"
}

class ChocolateIceCream : IceCream() {
    override fun getDescription() = "Chocolate ice cream"
}

abstract class ToppingDecorator() : IceCream() {
    abstract fun addTopping(): String
}

class HoneyToppingDecorator(private val iceCream: IceCream) : ToppingDecorator() {
    override fun getDescription() = iceCream.getDescription() + addTopping()
    override fun addTopping() = " With honey topping &"
}

class NutsToppingDecorator(private val iceCream: IceCream) : ToppingDecorator() {
    override fun getDescription() = iceCream.getDescription() + addTopping()
    override fun addTopping() = " With nuts topping &"
}

fun main() {
    val vanillaIceCreamWithHoneyTopping = HoneyToppingDecorator(VanillaIceCream())
    val vanillaIceCreamWithHoneyToppingDescription = vanillaIceCreamWithHoneyTopping.getDescription()
    println("You ordered: ${vanillaIceCreamWithHoneyToppingDescription.trimEnd('&')}")

    val strawberryCreamWithNutsTopping = NutsToppingDecorator(StrawberryIceCream())
    val strawberryCreamWithNutsToppingDescription = strawberryCreamWithNutsTopping.getDescription()
    println("You ordered: ${strawberryCreamWithNutsToppingDescription.trimEnd('&')}")

    val chocolateIceCreamWithHoneyAndNutsTopping = NutsToppingDecorator(HoneyToppingDecorator(ChocolateIceCream()))
    val chocolateIceCreamWithHoneyAndNutsToppingDescription = chocolateIceCreamWithHoneyAndNutsTopping.getDescription()
    println("You ordered: ${chocolateIceCreamWithHoneyAndNutsToppingDescription.trimEnd('&')}")
}

 

The output will be:

You ordered: Vanilla ice cream With honey topping 
You ordered: Strawberry ice cream With nuts topping 
You ordered: Chocolate ice cream With honey topping & With nuts topping 

 

As you can see you can mix decorators as you like to achieve the desired results. In the first order when the client asks for vanilla ice cream with honey topping, the getDescription() function in the HoneyToppingDecorator class forward the request to the IceCream object first after that it added the additional responsibility – which is in our case the topping-.

So as recap Decorator pattern allows us to attach additional responsibilities (Topping) to an object (IceCream object) dynamically without the need to subclass IceCream to extend its functionality.

And that’s it for the Decorator pattern, and as always you can refer to the GoF book for more.

So that’s all I have for this post and I always appreciate feedback and If you have any question please put your questions in the comments.

Till the next pattern.

Leave a Reply

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