The adapter pattern converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
This pattern allows us to use a client with an incompatible interface by creating an Adapter that does the conversion. This acts to decouple the client from the implemented interface, and if we expect the interface to change over time, the adapter encapsulates that change so that the client doesn’t have to be modified each time it needs to operate against a different interface.
Lets take an example i have been working on a project where we were only interacting with the oracle database and we had an interface which we were consistently using it across the application . Later we had a requirement where we had to also start interacting with the HSQLDB a in memory database for optimization purpose . We had a proprietary framework which we had to use across organization to interact with the HSQLDB and the interface that the framework had was not compatible with the interface we had so we decided to write a adapter for the same so that we can have consistency across application for db layer.
Below is the existing trait we had and for simplicity purpose i have added only two methods in the trait
trait DbType { def query[T](query: String, parameters: Array[String], rowMapper: RowMapper[T]):Vector[T]; def update(query: String, parameters: Array[String]); }
The new hsql trait
trait HsqlAccessor { def select(query:String,parameters:Vector[String]):ResultSet; def update(query:String,parameters:Vector[String]); }
So to make our DbType trait compatible with the HsqlAccessor trait we wrote an adapter as below
class HsqlAdapter(hsqlAccessor:HsqlAccessor) extends DbType { def query[T](query: String, parameters: Array[String], rowMapper: RowMapper[T]):Vector[T]= { var rs:ResultSet= hsqlAccessor.select(query, parameters.toVector) convertResultSet(rs, rowMapper) } def update(query: String, parameters: Array[String]) { hsqlAccessor.update(query, parameters.toVector) } def convertResultSet[T](rs:ResultSet, mapper:RowMapper[T]):Vector[T] = { // Method to convert the resultset into the list of Model using the // rowmapper return Vector[T](); } }
Some Domain classes used are below
case class MetricMetadata(id: Int, metricName: String, metricValueType: String) extends Model { }
class MetricMetadataMapper extends RowMapper[MetricMetadata] { def mapRow(rs: ResultSet, rowNum: Int): MetricMetadata = { MetricMetadata(rs.getInt("METRIC_METADATA_ID"), rs.getString("METRIC_NAME"), rs.getString("METRIC_VALUE_TYPE")); } }
import java.io.Serializable trait Model extends Serializable{ }
trait RowMapper[T] { def mapRow(rs:ResultSet,rowNum:Int):T; }
trait ResultSet { def getInt(column_name:String):Int; def getString(column_name:String):String; }
class HsqlAccessorImpl extends HsqlAccessor { def select(query:String,parameters:Vector[String]):ResultSet = { null; } def update(query:String,parameters:Vector[String])= { } }
Driver class
object Test extends App { import pattern.adapter.ImplicitObject._; var hsql:DbType=new HsqlAdapter(new HsqlAccessorImpl()) hsql.query("sql query", Array[String]("one","two"), new MetricMetadataMapper()) }
The scala way
We can use implicit classes to achieve what the adapter pattern does in scala. Below is an example
object ImplicitObject { implicit class HsqlAccessorScalaWay(var hsqlAccessor: HsqlAccessor){ def query[T](query: String, parameters: Array[String], rowMapper: RowMapper[T]): Vector[T] = { var rs: ResultSet = hsqlAccessor.select(query, parameters.toVector) convertResultSet(rs, rowMapper) } def update(query: String, parameters: Array[String]) { hsqlAccessor.update(query, parameters.toVector) } def convertResultSet[T](rs: ResultSet, mapper: RowMapper[T]): Vector[T] = { // Method to convert the resultset into the list of Model using the // rowmapper return Vector[T](); } } }
Driver class
object Test extends App { import pattern.adapter.ImplicitObject._; var hs: HsqlAccessor = new HsqlAccessorImpl(); hs.query("sql query", Array[String]("one", "two"), new MetricMetadataMapper()); }