Decorator - Design Pattern
Design Patterns: Elements of Reusable Object-Oriented Software
If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
What is Decorator Design Pattern?
"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."
When to Use?
While thinking about extending functionality, the first thing which is likely to occur to a new programmer is inheritance. However, inheritance may not be the ideal solution in all situations. When you inherit functionality through subclassing, the functionality is statically set at compile time and it doesn’t always lead to the most flexible nor maintainable designs. If you need to add new features, code modifications are required, which is a violation of the Open Closed Principle.
Instead, you can attach new responsibility to an object dynamically. This is exactly the intended use of the decorator pattern “Attach additional responsibilities to an object dynamically”. But how do you do this? By using Composition. With composition, you can dynamically add multiple new responsibilities to objects at run time. The great thing here is that the object doesn’t need to be aware of it, and your design conforms to the Open Closed Principle.
Let's deep dive into Decorator Design Pattern.
abstract class FlowerBouquet {
open var _description: String? = null
abstract fun cost(): Double
}
class RoseBouquet : FlowerBouquet() {
override fun cost(): Double = 12.0
init {
_description = "Rose bouquet"
}
}
class OrchidBouquet : FlowerBouquet() {
override fun cost(): Double = 29.0
init {
_description = "Orchid bouquet"
}
}
In the above diagram, we have an abstract class called FlowerBouquet
. where RoseBouquet
, OrchidBouquet
extends the abstract class FlowerBouquet
.
Now, here everything is perfect and we followed the Object Oriented Principles.
In the above diagram, we added some more concrete classes which extends the base class.
Let's say we want to some more additional objects with some customization, how do we do that? By adding more abstract method to the FlowerBouquet
class and implement it concrete classes?
Yes, we can definetly do that, But!. Let's say for some contrete classes, there is no need for additional customization but as we declared abstract method, so it should be implemented on concrete class also. If something changes in base class, child classes should be changed too. Let's say 100 classes extended from the base class, so those 100 classes should be updated according to the base class.
That's where we are braking the Design Principles and we shouldn't do it.
So, how can we solve this problem? By following Decorator Design Pattern, it's allow to add additional responsibilites to an object.
Take a look into the above diagram, we have added another abstract class named FlowerBouquetDecorator
.
abstract class FlowerBouquetDecorator : FlowerBouquet() {
abstract override var _description: String?
}
Here, we added an abstract variable _description
, becuase we want to provide some additional information as _description
.
Let's create another two classes which extends FlowerBouquetDecorator
and in the concrete classes we updated the _description
.
class PaperWrapper(private var flowerBouquet: FlowerBouquet) : FlowerBouquetDecorator() {
override var _description: String = flowerBouquet._description + ", paper wrap"
override fun cost(): Double {
return 3 + flowerBouquet.cost()
}
}
class RibbonBow(private var flowerBouquet: FlowerBouquet) : FlowerBouquetDecorator() {
override var _description: String = flowerBouquet._description + ", ribbon bow"
override fun cost(): Double {
return 6.5 + flowerBouquet.cost()
}
}
Now we can do some additional changes to an object in run-time. How?
Let's say we have an RoseBouquet
object and we want some customization to it. Simply, we can call PaperWrapper
which take FlowerBouquet
object as a parameter and PaperWrapper
will allow us to change RoseBouquet
object behaviour in run-time.
Let's take a look at a example:
val rose = RoseBouquet()
println("Without customization = ${rose._description}, ${rose.cost()}")
// Result - Without customization = Rose bouquet, 12.0
val roseWrapper = PaperWrapper(rose)
println("With customization = ${roseWrapper._description}, ${roseWrapper.cost()}")
//Result - With customization = Rose bouquet, paper wrap, 15.0
val orchid = OrchidBouquet()
println("Without customization = ${orchid._description}, ${orchid.cost()}")
//Result - Without customization = Orchid bouquet, 29.0
val orchidRibbon = RibbonBow(orchid)
println("With customization = ${orchidRibbon._description}, ${orchidRibbon.cost()}")
//Result - With customization = Orchid bouquet, ribbon bow, 35.5
And, that's how we attach additional functionalites to an object
in run-time without breaking any Design Principles.
Let's take a look at the full implementation in Kotlin
Follow Me on LinkedIn