Decorator Design Pattern in Kotlin

1. Definition

The Decorator Design Pattern allows you to add responsibilities to objects dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

In this tutorial, we will learn how to implement a Decorator Design Pattern using Kotlin programming language.

2. Problem Statement

Imagine you're building a coffee shop application. You start with basic coffee, but customers can add various condiments like milk, sugar, or whipped cream. As new condiments or types of coffee are introduced, subclassing each combination becomes impractical.

3. Solution

Use the Decorator pattern to wrap the original object (e.g., coffee) with additional functionality (e.g., milk or sugar). Each decorator wraps another object, forming layers of functionality that can be combined as needed.

4. Real-World Use Cases

1. GUI components where individual components can be decorated with scroll bars, borders, or shadows.

2. Middleware in web frameworks where request and response objects are decorated with logging, authentication, or caching.

5. Implementation Steps

1. Define an interface for the main object.

2. Create concrete components implementing the main object interface.

3. Create abstract decorator classes that also implement the main object interface.

4. Implement concrete decorators by inheriting from the abstract decorator.

6. Implementation in Kotlin Programming

// Main object interface
interface Coffee {
    fun cost(): Double
    fun description(): String
}
// Concrete component
class BasicCoffee : Coffee {
    override fun cost() = 2.0
    override fun description() = "Basic Coffee"
}
// Abstract decorator
abstract class CoffeeDecorator(private val coffee: Coffee) : Coffee {
    override fun cost() = coffee.cost()
    override fun description() = coffee.description()
}
// Concrete decorators
class MilkDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
    override fun cost() = super.cost() + 0.5
    override fun description() = super.description() + ", Milk"
}
class SugarDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
    override fun cost() = super.cost() + 0.2
    override fun description() = super.description() + ", Sugar"
}
fun main() {
    val coffee: Coffee = SugarDecorator(MilkDecorator(BasicCoffee()))
    println("${coffee.description()} costs \$${coffee.cost()}")
}

Output:

Basic Coffee, Milk, Sugar costs $2.7

Explanation:

1. Coffee is the main object interface, defining methods to get the cost and description.

2. BasicCoffee is a concrete component that implements the Coffee interface.

3. CoffeeDecorator is an abstract decorator that implements the Coffee interface and wraps another Coffee object.

4. MilkDecorator and SugarDecorator are concrete decorators that add their respective features to the coffee.

5. In the main function, a coffee with milk and sugar is created using decorators. The final cost and description are calculated based on all layers of decorators.

7. When to use?

Use the Decorator pattern when:

1. You need to add responsibilities to objects dynamically and transparently.

2. You want to keep new responsibilities separate.

3. Extending responsibilities using inheritance is impractical.

Comments