Aunque este blog no sea ni remotamente parecido a las mañanas de Arguiñano, hoy vamos a aprender a cocinar tartas…con Scala para solventar una cuestión de inyección de dependencias (¿cómo?¿lo qué?)
Inyección de dependencias
Supongamos que trabajamos coordinando un equipo de diseño de coches Bercedes Menz. Hay varias áreas de trabajo.
Un grupo se encarga de diseñar el chasis, otro de diseñar las ruedas y otro de diseñar el motor.
¿Cómo especificarías la composición del coche?
Empecemos creando las clases para Coche, Rueda, Chasis y Motor. Y les añadimos un poco de funcionalidad (para que tengan algo de chicha).
class Coche { val ruedas : List[_] = ??? val chasis = ??? val motor = ??? } trait Rueda { val coeficienteAdherencia: Float } trait Chasis { val numero_puertas: Int } trait Motor { val potencia: Int }
Ahora es cuando, de alguna forma, debemos indicar que Coche depende de todos y cada uno del resto de componentes, pero imaginad que tenemos que usar métodos estáticos para poder implementar las distintas piezas.
¿Qué opciones tenemos? Por una parte podemos parametrizar el coche y agregar esos componentes como miembros de la clase:
class Coche[R<:Rueda,C<:Chasis,M<:Motor]{ val ruedas: Lista[R] = List() val chasis: C = C.newInstance() val motor: M = C.newInstance() }
Mmmm no compila,tío ¿Por qué?
Porque hasta mi abuela se daría cuenta de que esos métodos ‘newInstance’ se encuentan definidos en el companion de las clases C y M. Bueno, se podría decir, pues en vez de parametrizarlo como
R<:Rueda
lo cambiamos por
R<:Rueda.type
Vale, y ahora es cuando te das cuentas de que llevas muchas horas programando a base de café y de que entonces parametrizar esa clase es absurdo, ya que estás haciendo explícitos los parámetros.
¡A cocinar, Carmen de Toledo!
La opción que se propone es la de usar el Cake Pattern, considerada como la respuesta de Scala para el patrón de inyección de dependencias (DI). Este patrón de diseño orientado a objetos, anima a que se le suministren los objetos instanciados en vez de crearlos la propia clase. Si lo aplicamos a nuestro ejemplo, podríamos indicar en la expresión del selftype, que nuestra factoría de coches tiene dependencia de 3 componentes:
trait FactoriaCoches { estaFactoriaDependeDe: ComponenteRuedas, ComponenteChasis, ComponenteMotor => case class Coche(val ruedas: List[Rueda], motor: Motor, chasis: Chasis) def fabricarCoche = Coche( List(new Rueda, new Rueda, new Rueda, new Rueda), //...colina abajo Motor("NumeroDeBastidorNoInventadoParaNadaAunqueIgualDeLargo",90), Chasis(3)) }
Nótese que se pueden definir miembros y métodos basándose en el hecho de que, en el momento de composición de la factoría, se utilizarán dichos componentes.
Y ahora los componentes se definirían como:
trait ComponenteRueda{ class Rueda{ val coeficienteAdherencia: Float = 0.85 } def tiempoFrenada(velocidad: Float, rueda: Rueda): Float = { //Aquí van unos cálculos complicados del copón. rueda.coeficienteAdherencia * 0.1 //El resultado es, por ejemplo: 1 } } trait ComponenteChasis{ case class Chasis(val numeroPuertas: Int) } trait ComponenteMotor{ case class Motor(val numeroBastidor: String, val potencia: Int) def calculaPotencia(numeroBastidor: String): Int= { //Un par de llamadas a tráfico y te dicen que la potencia es 115 } }
Es de apreciar el uso de inner classes en los componentes.
Una vez definidos los componentes, ya podemos componer nuestro objeto factoría:
object MiFactoriaDeCoches extends FactoriaCoches with ComponenteRueda with ComponenteChasis with ComponenteMotor val miCocheDeLosDomingos = MiFactoriaDeCoches.fabricarCoche
Si se quisiera añadir otro tipo de rueda o de motor, bastaría con extender al componente en cuestión y luego usarlo en la instanciación del objecto:
trait OtroTipoDeComponenteRueda extends ComponenteRueda{ //... } object OtraFactoriaDeCoches extends FactoriaCoches with OtroTipoDeComponenteRueda with ComponenteChasis with ComponenteMotor
En la próxima entrada veremos tipos estructurales y cómo podemos expresar inyección de dependencias basándonos en dichos tipos.
Y hasta aquí nuestra tarta de hoy.
Rica, rica, … y con fundamento 😉