Decorator Pattern in Scala

Saurabh Kishore Tiwari
The Thought Mill
5 min readMay 1, 2023

--

Ah, the Decorator pattern! It’s like the fashion world’s equivalent of layering clothes for extra warmth, except it’s for adding functionality to objects. In Scala, it’s like adding a fancy scarf or hat to your basic outfit to jazz it up.

So, let’s say you have an object that’s a little plain and boring. You could try to add some functionality to it by changing its code directly, but that would be like sewing patches onto your clothes — messy and irreversible. Instead, you can use the Decorator pattern to add new behaviour to an object without modifying its original code.

Think of it like wrapping a present with multiple layers of wrapping paper and bows. Each layer adds something extra, but you can still unwrap the present to get to the original gift inside.

In Scala, you can use the Decorator pattern to create a new class that wraps around an existing class and adds new behaviour to it. It’s like putting a new layer of code on top of the original class, without changing anything about the original class itself.

So, the next time you’re feeling like your code needs a bit of pizzazz, try using the Decorator pattern to spice things up. Just don’t forget to accessorise with some fancy shoes or a fun piece of jewellery!

For me, it’s always been easy to understand something if I can visualise it, so let’s look at the UML diagram

Let’s breakdown the structure now

Component
1. Defines the interface for objects that can have responsibilities added to them dynamically.

ConcreteComponent
1. Defines an object to which additional responsibilities can be attached .

Decorator
1. Maintains a reference to a Component object and defines an interface that conforms to Component’s interface.

ConcreteDecorator
1. Adds responsibilities to the component.

Implementation

Imagine a pizza store. You’re selling 3 different kinds of pizza — Farmhouse, IndieTandoori and VegExtravaganza. You offer two different kinds of bases — DoubleBase and ThinCrust. You’re a new store so you are offering only 2 toppings right now — ExtraCheese and ExtraOnion.

Imagine the combinations of offering possible?
3 pizza and 2 basses = 3 x 2 = 6 offerings
3 pizza, 2 bases, 2 toppings = 12 offerings

To cover all combinations, you’ll need 12 classes. Let’s say you are happy with the profit so you decide to increase the number of pizzas or bases or the toppings. There will always be a steep rise in number of classes to handle all the combinations.

Now, why the decorator pattern is the right design?
You have a main component — PIZZA. You need to decorate your pizza according to the customer’s requirement.
What all can be decorators? Bases and Toppings.

UML for the same will look like:

There’s an extra layer of abstraction so that it’s easy to segregate the edible items like Pizza and different decorators like Base and Topping.

Code

Pizzas.scala

trait Edible:
val name: String
val price: Double
def getPrice: Double
def description: String

sealed trait Pizza extends Edible

class Farmhouse extends Pizza:
override val name: String = "Farmhouse"
override def description: String = "Farm House"
override val price: Double = 299.99
override def getPrice: Double = price

class IndieTandooriPizza extends Pizza:
override val name: String = "Indie Tandoori Pizza"
override def description: String = "Indie Tandoori Pizza"
override val price: Double = 299.99
override def getPrice: Double = price

class VegExtravaganza extends Pizza:
override val name: String = "Veg Extravaganza"
override def description: String = "Veg Extravaganza"
override val price: Double = 299.99
override def getPrice: Double = price

Bases.scala

trait EdibleDecorator extends Edible

sealed trait Base(edible: Edible) extends EdibleDecorator

class DoubleBase(private val edible: Edible) extends Base(edible):
override val name: String = "Double Base"
override def description: String = s"${edible.description}, Double Base"
override val price: Double = 49.99
def getPrice: Double = edible.getPrice + this.price

class ThinCrust(private val edible: Edible) extends Base(edible):
override val name: String = "Thin Crust"
override def description: String = s"${edible.description}, Thin Crust"
override val price: Double = 149.99
def getPrice: Double = edible.getPrice + this.price

Toppings.scala

sealed trait Topping(edible: Edible) extends EdibleDecorator

class ExtraCheese(private val edible: Edible)extends Topping(edible):
override val name: String = "Extra Cheese"
override def description: String = s"${edible.description}, Extra Cheese"
override val price: Double = 19.99
override def getPrice: Double = edible.getPrice + this.price

class ExtraOnion(private val edible: Edible) extends Topping(edible):
override val name: String = "Extra Onion"
override def description: String = s"${edible.description}, Extra Onion"
override val price: Double = 29.99
override def getPrice: Double = edible.getPrice + this.price

MainRunner.scala

object MainRunner:
def main(args: Array[String]): Unit =
var pizza1: Edible = new IndieTandooriPizza()
println(s"Current Price is ${pizza1.getPrice}")

pizza1 = new DoubleBase(pizza1)
println(s"Current price is ${pizza1.getPrice}")

pizza1 = new ExtraCheese(pizza1)
println(s"Current price is ${pizza1.getPrice}")

In future, if you need to add more pizzas or toppings, you can just create a class for it and extend the required trait for it. This was possible because we followed something called Interface-Segregation principle (which states that we should have multiple smaller interfaces rather than having a big one)

Now… to increase your customer base, you decide to offer discount to your customers. So you decide to offer 40 and 60 percent discount(Yeah, you are very generous… now smile 😊)

So, you just spin up a trait offer and make concrete classes for 40 and 60 percent discount.

Offers.scala

sealed trait Offer:
def getPrice: Double

class offer_40(private val edible: Edible) extends Offer:
override def getPrice: Double = edible.getPrice * 0.6

class Offer_60(private val edible: Edible) extends Offer:
override def getPrice: Double = edible.getPrice * 0.4

Suppose, tomorrow the competition grows and you need to give discounts instantly, just enable this piece of code and boom, you are production ready. (I know, Unit tests and Functional tests are pending. You handle that.)

Known Uses and Related Patterns

If you’re from JAVA background, at times you would have often wrapped one object over the other, that resembles decorator pattern; basically, when one has an object that requires functionality extension or one has to recursively rewrap or change the functionalities of an object according to the requirements, dynamically without affecting other objects of the class. For extending functionality, decorators are a versatile alternative to subclassing.

Adapter, Composite and Strategy patterns are some patterns which resembles decorator pattern but their implementations differ.

--

--