title | category | language | tag | ||
---|---|---|---|---|---|
Decorator |
Structural |
en |
|
- Wrapper
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Real-world example
There is an angry troll living in the nearby hills. Usually, it goes bare-handed, but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon.
In plain words
Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.
Wikipedia says
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified.
Programmatic Example
Let's take the troll example. First of all we have a SimpleTroll
implementing
the Troll
interface:
interface Troll {
fun attack()
fun fleeBattle()
val attackPower: Int
}
class SimpleTroll : Troll {
override fun attack() {
logger.info("The troll tries to grab you!")
}
override fun fleeBattle() {
logger.info("The troll shrieks in horror and runs away!")
}
override val attackPower: Int
get() = 10
}
Next, we want to add a club for the troll. We can do it dynamically by using a decorator:
class ClubbedTroll(private val decorated: Troll) : Troll {
override fun attack() {
decorated.attack()
logger.info("The troll swings at you with a club!")
}
override fun fleeBattle() {
decorated.fleeBattle()
}
override val attackPower: Int
get() = decorated.attackPower + 10
}
Here's the troll in action:
// simple troll
val logger = LoggerFactory.getLogger("com.yonatankarp.decorator")
// simple troll
logger.info("A simple looking troll approaches.")
val troll = SimpleTroll()
troll.attack()
troll.fleeBattle()
logger.info("Simple troll power: ${troll.attackPower}.\n")
// change the behavior of the simple troll by adding a decorator
logger.info("A troll with huge club surprises you.")
val clubbedTroll = ClubbedTroll(troll)
clubbedTroll.attack()
clubbedTroll.fleeBattle()
logger.info("Clubbed troll power: ${clubbedTroll.attackPower}.\n")
Program output:
A simple looking troll approaches.
The troll tries to grab you!
The troll shrieks in horror and runs away!
Simple troll power: 10.
A troll with huge club surprises you.
The troll tries to grab you!
The troll swings at you with a club!
The troll shrieks in horror and runs away!
Clubbed troll power: 20.
Decorator is used to:
- Add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.
- For responsibilities that can be withdrawn.
- When extension by subclassing is impractical. Sometimes a large number of independent extensions are possible and would produce an explosion of subclasses to support every combination. Or a class definition may be hidden or otherwise unavailable for subclassing.