Romman Sabbir
Romman Sabbir | Official

Romman Sabbir | Official

Decorator - Design Pattern

Decorator - Design Pattern

Design Patterns: Elements of Reusable Object-Oriented Software

Romman Sabbir's photo
Romman Sabbir
·Jan 23, 2022·

4 min read

Subscribe to my newsletter and never miss my upcoming articles

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.

decorator_1.png

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.

decorator_2.png

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.

decorator_3.png

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

 
Share this