Decorator Pattern: Scala

Photo by Lisa Fotios on Pexels.com

What is Decorator Pattern?

This pattern lets a programmer add more functionality to an object without impacting other objects of the class. So off course without inheritance

Why do you need it?

At times we need our objects to have more functionality based on different use cases. One way to achieve this is via Inheritance, but it is not always possible and it impacts all objects of the class, which might not be suitable.

Let’s build something in Scala to understand why we need it and how we can use Scala Traits to achieve it.

DB trait represents a Key,Value Database with simple functions for fetching, updating and deleting data.

trait DB {
  def getData(k: String): String
  def putData(k: String, v: String): Unit
  def deleteData(k: String): Unit
}

We have a SimpleDB which implements this trait. It holds key -> value pairs in memory using a simple mutable map.

class SimpleDB extends DB {
  private val dbData = scala.collection.mutable
    .Map[String, String]()

  override def putData(key: String, value: String): Unit = {
    dbData.put(key, value)
  }

  override def deleteData(key: String): Unit = {
    dbData.remove(key)
  }

  override def getData(id: String): String = {
    dbData.get(id).getOrElse("no value found")
  }
}

We would like to separate clients based on their access rights. Some clients may be read-only access, some may also have write-access and administrators can have delete-access as well.

Let’s define some traits for different types of access.

Provides read access only

trait ReadOnly {
  val db: DB
  def getData(id: String): String = db.getData(id)
}

Provides additional write access

trait Writer extends ReadOnly {
  val db: DB
  def deleteData(key: String) = db.deleteData(key)
  def putData(key: String, value: String) = db.putData(key, value)
}

So now we have a simple database and some interfaces to access data based on access rights.

Let’s define simple clients to access our Database.

class DBClient(override val db: DB) extends ReadOnly

By default all clients will have a read-only access.

Now how do we create clients with write access as well?

We have two options:

  • Create a new client and extend Writer trait.
    • This is ok if you have only two such cases, but what if you have more flavors ? Will you create new clients for each one?
  • On the same client extend Writer trait as well.
    • This means there won’t be any read-only client all will have write access as well.
    • Also sometimes you won’t have control on changing the class definition, may be because its a third party library?

Third option goes something like this:

//Create a db
val simpleDB = new SimpleDB
//A read only client
val readOnlyClient = new DBClient(simpleDB)

when we create a new client (read-only) using a with keyword you can add additional feature of other Traits.

val writeableClient = new DBClient(simpleDB) with Writer

What this means is that only this object of DBClient will have the write-access without impacting any other object.

Just to have another example lets say to improve the performance of read calls we want to add caching functionality to our clients. How do we do that? Another trait and then mix it up when we need it.

Caching trait can add caching functionality to any client. mutable.Map ‘s getOrElseUpdate takes a key and function to fetch the value if not already present in the cache. Here super.getData will call ReadOnly clients getData function

//Trait which adds a caching functionality
trait Caching extends ReadOnly {
 //caches data
 val data = scala.collection.mutable.Map[String, String]()
 override def getData(key: String): String = {
   data.getOrElseUpdate(key, super.getData(key))
 }
}

Create a caching client as follows :

val cachingClient = new DBClient(simpleDB) with Caching

Test our clients:

readOnlyClient.getData("1")
writeableClient.putData("3", "value of 3")
readOnlyClient.getData("3")
cachingClient.getData("3")
writeableClient.deleteData("3")
readOnlyClient.getData("3")
Note :  This is just a simple implementation to demonstrate Scala mixin as Decorator pattern. The caching and database implementations are far from ideal and in real world we will have more sophisticated libraries to access database and implement caching. But it can still be taken as a general pattern to wrap underlying clients to add or hide functionality. 

Cheers,
Kaivalya

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s