The Adapter Design Pattern in Scala using implicits

Comparing to the standard implementations  of Adapter Pattern by GoF – Scala have a better, shorter, and fancier way to implement it. In this post I’m going to explain and guide how to implement and use the Adapter design pattern in Scala (using implicits).

Let’s start with a short reminder – the adapter pattern is a pattern to convert the interface of a class into another interface clients expect. Many people saying that design patterns are missing features of the language – so we’ll see how Scala making it easier for us.

Scala Code Samples – Adapter Design Pattern

gist of Scala and Java code samples presented here – link to gist

General sample (Adaptor/Adaptee)

Assume we have some existing code –

trait Adaptor {
  def doAdaptor
}

class Client(adaptor: Adaptor){
  def foo = adaptor.doAdaptor
}

And we want to use the Client with the newly written non compatible “Adaptee” class

trait Adaptee {
  def doAdaptee }

What we need to do is to write the “adapter”. In scala, all needed is to have the implicit conversion in the scope –

implicit def adaptee2Adaptor(adaptee: Adaptee): Adaptor = {
  new Adaptor {
    override def doAdaptor: Unit = adaptee.doAdaptee
  }
}

Now, we’re ready to go. Just use the Client directly with the adaptee and the Scala compiler will automatically call the adapter method (adaptee2Adaptor) to do the correct conversion.

val adaptee: Adaptee = ...
val client = new Client(adaptee)

An important note – the underline at the “adaptee” is IntelliJ’s way to show us that an implicit conversion took place here. This is very important when reading Scala code.

A concrete sample (AirConditioner)

Now, let’s do the same with a real world example. We have an AirConditioner class which have setTemperature method that gets Celsius as the parameter, we’ll create an adapter for Fahrenheit.

case class Celsius(degrees: Double)
case class Fahrenheit(degrees: Double)

class AirConditioner{
  def setTemperature(celsius: Celsius) = println(s"Set to ${celsius.degrees}")
}

implicit def fahrenheit2Celsius(fahrenheit: Fahrenheit): Celsius = new Celsius((fahrenheit.degrees - 32) * 5/9)

new AirConditioner().setTemperature(new Fahrenheit(75))
Set to 23.88888888888889

Advantages/Disadvantages

Easier (&shorter) usage – reduce boilerplate code and noise Independance of client/adaptee/adapter. The only connection is that the implicit method should be in scope. + Scala compiler will automatically decide which Adapter should be used. If more than one in scope a compilation error will occur and the developer will have to do the conversion explicitly.

Extensibility – We can use the client with different adoptees (set AirConditioner temperature using different temperature measuring methods)

auto complete cluttering with all possible conversions (or confusion if same method name exists in different conversions)
– Adds an implicit level of indirection which adds to the complexity, therefore might be harder to understand (Modern IDE(i.e. IntelliJ) will help to minimize it with underlining these conversions. To see the conversion [Mac: control + Q])

adaptee autocomplete sample

Explicit implicit

The danger zone – Implicit conversion is a very powerful tool which should be used with care. Especially dangerous when using on common types like Int, List, Tupel, Option etc … So another way to have easy conversion without the danger zone is using explicit implicit, using Scala feature called implicit classAgain, the implicit class must be in scope and in addition we need to call the toCelsius method.

implicit class FahrenheitOps(temp: Fahrenheit) {
  def toCelsius = fahrenheit2Celsius(temp)
}
new AirConditioner().setTemperature(new Fahrenheit(75).toCelsius)

An example from Scala library

Scala provides both ways to convert from Java collections to Scala collections and vice versa. The developer can choose which way he prefers by choosing which namespace to import (JavaConversions -A collection of implicit conversions supporting interoperability between Scala and Java collections. & JavaConverters – A collection of decorators that allow converting between Scala and Java collections using asScala and asJava methods.). More about the differances from stackoverflow.com – http://stackoverflow.com/questions/8301947/what-is-the-difference-between-javaconverters-and-javaconversions-in-scala

 

The silver bullet – Type-class pattern

This pattern is a bit more complex but it gives you all the benefits of implicit conversions without the danger and ugly parts of using implicit conversion.

case class Celsius(degrees: Double)
 
case class Fahrenheit(degrees: Double)
 
trait ToCelsius[From] {
    def convert(source: From): Celsius
}
 
class FahrenheitConverter extends ToCelsius[Fahrenheit] {
    override def convert(source: Fahrenheit): Celsius = new Celsius((source.degrees 32) * 5 / 9)
}
 
class CelsiusIdentityConverter extends ToCelsius[Celsius] {
    override def convert(source: Celsius): Celsius = source
}
 
class AirConditioner {
    def setTemperature[T](degrees: T)(implicit ev: ToCelsius[T]): Unit = {
      val converter = implicitly[ToCelsius[T]]
      val asCelsius = converter.convert(degrees)
      println(sSet to $asCelsius)
    }
}
 
implicit val fahrenheitToCelsius = new FahrenheitConverter
implicit val celsiusIdentity = new CelsiusIdentityConverter
 
val airConditioner = new AirConditioner()
airConditioner.setTemperature(new Fahrenheit(75))
airConditioner.setTemperature(new Celsius(23))

We’re using our Celsius AirConditioner and we only need implicit definition of converter in scope.

* Note:
def setTemperature[T: ToCelsius](degrees: T): Unit unfolds to -> def setTemperature[T](degrees: T)(implicit ev: ToCelsius[T]): Unit

 

Additional readings

Wikipedia – Adapter pattern – https://en.wikipedia.org/wiki/Adapter_pattern
Scala documentation – Implicits – http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html
Programming in Scala, First Edition – Implicit conversions – http://www.artima.com/pins1ed/implicit-conversions-and-parameters.html

Leave a Reply