Pages

Sunday, September 7, 2014

HowTo:: Design patterns with Scala: GoF Structural Pattern Adapter and power of Trait

  
Img.1.:: Parts of Adapter patterns

   In this example I want to show power of trait usage in 2 different ways of Adapter Structural pattern implementations. There are couple of reasons why could be worth to think about the Adapter pattern. Basically the intent is to convert interface of the one type into the interface that client expects to use.
Pattern can be divided into the following parts (Img.1.):
1. Client - is an object that requires functionality provided by Adaptee but it's exposed by Adapter
2. Adapter - is an object that converts the interface expected by the client into the interface provided by Adaptee 
3. Adaptee - is an object that provides the implementation and the interface invoked by the Adapter

in both Adapter examples we do use same Client implementation: 
class Client(service: Service) {
  def doWork = {
    service.invoke
  }
}
and Service trait which defines method process:
trait Service {
  def process(): Unit
}
  Already from the Main Scala Object AdapterTest.scala is noticeable the 1st warm up of the Scala trait power which will be fully exposed by AdaptorTrait latter.
object AdapterTest {

  private val logger = LoggerFactory.getLogger(getClass)

  def main(args: Array[String]) ={
    logger.debug("Test Adapter pattern")
    val adaptor = new AdaptorOne
    val clientWithoutTrait = new Client(adaptor)
    clientWithoutTrait.doWork()

    val adaptorTraitOne = new AdapteeOne with AdaptorTrait
    val adaptorTraitTwo = new AdapteeTwo with AdaptorTrait
    val clientWithTrait1 = new Client(adaptorTraitOne)
    clientWithTrait1.doWork()

    val clientWithTrait2 = new Client(adaptorTraitTwo)
    clientWithTrait2.doWork()
  }

}

1. Object based implementation without trait we do use classical scala class definition of the AdapteeOne that provides implementation to the action() that Client wants.
class AdapteeOne extends Adaptee{
  private val logger = LoggerFactory.getLogger(getClass)
  def action() = { logger.debug("Adaptee One Action One")}
}
  The Adapee trait is used to make code standardised and also to point out limits of the presented Object implementation .
trait Adaptee {
  def action()
}
  Currently has been implemented 1st part of the blog post -> Adapter in the classical object way.

2. trait based approach is inspired by having two, almost same, objects (example: CAR1, CAR2) and both has different implementation of the action() function (example: both cars can ride). We again use AdapteeOne (previously implemented) and we create AdapteeTwo scala class.
class AdapteeTwo extends Adaptee{
  private val logger = LoggerFactory.getLogger(getClass)
  def action() = { logger.debug("Adaptee Two Action")}
}
   Both classes extend Adaptee trait which method is served to the AdaptorTrait that extend the Service (also previously defined)
trait AdaptorTrait extends Service{
  self: Adaptee =>
  def process() = action()
}
   Now we are ready also with 2nd Adapter pattern example usage. Trait usage (AdaptorTrait) allows to be mixed into a type either directly at the type declaration time or an object creation time.  This simply allows latter object behaviour extension. 
Traits go ahead! implementation with them is much easier, readable and extendable  + testable, less code

No comments: