Geeknarrator

computer science

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
computer science

Functional Refactoring

One of the biggest challenges most software engineers face is, to maintain a badly written code. Refactoring code is a very difficult task, which can break a lot of things in a so called working software. There are only two […]

computer science

Hypermedia Driven Rest APIs – HATEOAS

What is HATEOAS ? (Hate-OAS, Hateous or any other pronunciation) Hypermedia as the Engine of Application State It basically means that your application state should be Hypermedia driven. Hypermedia ?  – Basically hyperlinks to other resources. Which also means that, […]

  • 1
  • 2