1. Overview
The Decorator Pattern is a design pattern that allows adding new functionality to an existing object without altering its structure and without affecting the behavior of other objects from the same class.
In this tutorial, we will cover some efficient approaches for the implementation of this pattern in Kotlin.
2. The Decorator Pattern
The Decorator pattern allows us to add behavior either statically or dynamically by providing an enhanced interface to the original object. The static approach can be achieved using inheritance, overriding all the methods of the main class and adding the extra functionality that we want.
As an alternative to inheritance and to reduce the overhead of subclassing, we can use composition and delegation to add additional behavior dynamically. In this article, we’ll follow these techniques for the implementation of this pattern.
Let’s consider the next example of a Christmas tree object that we want to decorate. The decoration does not change the object itself; it’s just that in addition to the Christmas tree, we’re adding some decoration items like garland or bubble lights or any other type:
Let’s now see the implementation of the pattern using this example.
3. Implementation
First, we need to create the ChristmasTree generic interface:
interface ChristmasTree {
fun decorate(): String
}
Now, let’s define the implementation for this interface:
class PineChristmasTree : ChristmasTree {
override fun decorate() = "Christmas tree"
}
Next, we’ll see the two strategies to decorate ChristmasTree objects.
3.1. Decorate by Composition
When using composition to implement the Decorator pattern, we’ll need an abstract class that will act as the composer or decorator for our target object:
abstract class TreeDecorator
(private val tree: ChristmasTree) : ChristmasTree {
override fun decorate(): String {
return tree.decorate()
}
}
We’ll now create the decorating element. This decorator will extend our abstract TreeDecorator class and will modify its decorate() method according to our requirement:
class BubbleLights(tree: ChristmasTree) : TreeDecorator(tree) {
override fun decorate(): String {
return super.decorate() + decorateWithBubbleLights()
}
private fun decorateWithBubbleLights(): String {
return " with Bubble Lights"
}
}
Now, we can create our decorated ChristmasTree object:
fun christmasTreeWithBubbleLights() {
val christmasTree = BubbleLights(PineChristmasTree())
val decoratedChristmasTree = christmasTree.decorate()
println(decoratedChristmasTree)
}
3.2. Decorate by Delegation
The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively, requiring zero boilerplate code. This feature makes it easy to create decorators using class delegation with the use of the by keyword.
We’ll now define the class that can implement the ChristmasTree interface by delegating the decorator() method to a specified object:
class Garlands(private val tree: ChristmasTree) : ChristmasTree by tree {
override fun decorate(): String {
return tree.decorate() + decorateWithGarlands()
}
private fun decorateWithGarlands(): String {
return " with Garlands"
}
}
And now, we can create our decorated ChristmasTree object:
fun christmasTreeWithGarlands() {
val christmasTree = Garlands(PineChristmasTree())
val decoratedChristmasTree = christmasTree.decorate()
println(decoratedChristmasTree)
}
4. Conclusion
In this article, we’ve explored some efficient approaches to implement the Decorator Pattern in Kotlin. This pattern is useful when we want to add behavior, or when we want to enhance or even remove functionalities for specific objects. We also saw that Kotlin offers a native support feature to implement this pattern with class delegation.
As always, the complete code for this article is available over on GitHub.