Kotlin Delegates: The Secret Ingredient for Decorator Pattern!
What is Decorator pattern?
The decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.
What is Delegation?
Delegation pattern is an object-oriented design pattern that allows object composition to achieve the same code reuse as inheritance.
Show me the code!
Let’s understand decorator pattern with an example. We have a class UserRepositoryImpl
that implements a UserRepository.
UserRepositoryImpl
provides functionality to get and add users.
interface UserRepository {
fun get(userName:String):String?
fun set(userName: String,user:String)
}
class UserRepositoryImpl : UserRepository {
private val userList: MutableMap<String, String> =
mutableMapOf("superman" to "Clark Kent", "batman" to "Bruce Wayne")
override fun get(userName: String): String? {
return userList[userName]
}
override fun set(userName: String, user: String) {
userList[userName] = user
}
}
Now suppose you want to add a length validation check for userName without changing the existing object’s behavior and structure.
We make use of a decorator class. Taking advantage of Kotlin we can create a decorator with zero boilerplate. Kotlin provides by
delegates to achieve this. Note that we are not modifying the UserRepositoryImpl
class UsernameValidator (private val repository: UserRepository) : UserRepository by repository{
override fun set(userName: String, user: String) {
require(userName.length in MIN_NAME_LENGTH..MAX_NAME_LENGTH){
"user name is not of valid length"
}
repository.set(userName,user)
}
companion object{
private const val MAX_NAME_LENGTH = 20
private const val MIN_NAME_LENGTH = 4
}
}
Usage
val userRepository = UserRepositoryImpl()
val userValidator = UsernameValidator(userRepository)
userValidator.get(userName)
The classic debate here will be, why we are not using inheritance and creating a subclass to achieve the behavior we want.
Yes, we can use inheritance but we usually go for inheritance when we have a clear and stable relationship between classes that share common attributes and behaviors.
Using the decorator we create a wrapper object with the existing object without creating a new subtype of object. we can use the decorator pattern to compose different behaviors at runtime based on the context or user preferences.
Some of the common use cases of decorator patterns are logging, caching, encryption, or validation features to an object without modifying its original behavior.
Note: Things to consider before going for a decorator pattern
- We need to be able to receive the object we’re decorating.
- We need to be able to keep a reference to the object.
- We need to be able to extract an interface or have one provided.
Bonus
We can add operator
keyword in function definitions of UserRepository
interface UserRepository {
operator fun get(userName:String):String?
operator fun set(userName: String,user:String)
}
Now we can take advantage of Kotlin syntactic sugar, we can use ours UserRepository
like this.
repository[userName] = user // set
repository[userName] // get