Decorator Design Pattern in Scala

1. Definition

The Decorator Design Pattern dynamically adds/overrides behaviour in an object without altering its structure. It's a structural pattern that involves a series of decorator classes that are used to wrap concrete components. Decorator classes mirror the type of the components they decorate but add or override behavior.

2. Problem Statement

You want to add functionalities to individual objects, not to an entire class. Using inheritance to add responsibilities isn't scalable or flexible as it's static and applies to an entire class.

3. Solution

Use the Decorator pattern to allow for the dynamic wrapping of objects to extend their functionalities. With this pattern, new functionalities can be added to an object without creating a new subclass.

4. Real-World Use Cases

1. GUI toolkits where individual components can be decorated with additional behaviors, e.g., adding scroll bars to windows.

2. Middleware systems, adding functionalities like logging, transaction management, etc. to existing components.

3. Enhancing reading/writing operations in streams.

5. Implementation Steps

1. Define an interface for the component you want to decorate.

2. Create concrete components implementing this interface.

3. Create decorator classes that also implement this interface, and have a reference to the component.

6. Implementation in Scala Programming

// Step 1: Define a common interface for components
trait Beverage {
  def cost: Double
  def description: String
}
// Step 2: Concrete components
class Coffee extends Beverage {
  def cost: Double = 5.0
  def description: String = "Coffee"
}
// Step 3: Decorator classes
class Sugar(beverage: Beverage) extends Beverage {
  def cost: Double = beverage.cost + 1.0
  def description: String = beverage.description + ", Sugar"
}
class Milk(beverage: Beverage) extends Beverage {
  def cost: Double = beverage.cost + 2.0
  def description: String = beverage.description + ", Milk"
}
// Client code
object Main extends App {
  val coffeeWithSugarAndMilk = new Sugar(new Milk(new Coffee))
  println(s"${coffeeWithSugarAndMilk.description} costs ${coffeeWithSugarAndMilk.cost}")
}

Output:

Coffee, Milk, Sugar costs 8.0

Explanation:

1. A common interface Beverage is defined with methods cost and description.

2. The Coffee class, a concrete component, implements this interface.

3. Sugar and Milk are decorators. They have a reference to a Beverage and they override the methods to add their own behavior.

4. In the client code, a Coffee object is wrapped with Milk and then with Sugar. When querying its cost and description, it computes the combined cost and description of all the decorators and the original coffee.

7. When to use?

The Decorator pattern is particularly useful when:

1. You need to add responsibilities to individual objects, not entire classes.

2. You want to add responsibilities dynamically and remove them as well.

3. Extending functionalities using subclasses is impractical or cumbersome.


Comments