¿El patrón de Scala Cake fomenta las dependencias codificadas?
dependency-injection traits (1)
Todavía estoy tratando de aprender el patrón de Cake de Scala. Me parece que le da la ventaja de centralizar su configuración de "Componentes", así como la capacidad de proporcionar implementaciones predeterminadas para esos componentes (que, por supuesto, son reemplazables).
Sin embargo, el uso de rasgos propios para describir dependencias parece mezclar el área de preocupaciones. El propósito del Componente (creo) es abstraer las diferentes implementaciones para ese componente. Pero la lista de dependencia descrita en el Componente es en sí misma una preocupación de implementación .
Por ejemplo, digamos que tengo una base de datos llena de widgets, un registro que me permite buscar tipos específicos de widgets y algún tipo de algoritmo que usa el registro para procesar los widgets:
case class Widget(id: Int, name:String)
trait DatabaseComponent {
def database: (Int => Widget) = new DefaultDatabase()
class DefaultDatabase extends (Int => Widget) {
// silly impl
def apply(x: Int) = new Person(x, "Bob")
}
}
trait RegistryComponent {
this: DatabaseComponent => // registry depends on the database
def registry: (List[Int] => List[Widget]) = new DefaultRegistry()
class DefaultRegistry extends (List[Int] => List[Widget]) {
def apply(xs: List[Int]) = xs.map(database(_))
}
}
trait AlgorithmComponent {
this: RegistryComponent => // algorithm depends on the registry
def algorithm: (() => List[Widget]) = new DefaultAlgorithm()
class DefaultAlgorithm extends (() => List[Widget]) {
// look up employee id''s somehow, then feed them
// to the registry for lookup
def apply: List[Widget] = registry(List(1,2,3))
}
}
Y ahora puedes juntarlo en alguna configuración central:
object Main {
def main(args: Array[String]) {
val algorithm = new AlgorithmComponent() with RegistryComponent with DatabaseComponent
val widgets = println("results: " + algorithm.processor().mkString(", "))
}
}
Si quiero cambiar a una base de datos diferente, puedo inyectarla fácilmente cambiando mi mixin:
val algorithm = new AlgorithmComponent() with RegistryComponent with SomeOtherDatabaseComponent
Pero ... ¿y si quiero mezclar un componente de registro diferente que no utiliza una base de datos ?
Si trato de crear una subclase en el RegistryComponent con una implementación diferente (no predeterminada), el RegistryComponent insistirá en que incluya una dependencia DatabaseComponent. Y tengo que usar RegistryComponent, porque eso es lo que requiere el AlgorithmComponent de nivel superior.
¿Me estoy perdiendo de algo? En el momento en que uso un tipo propio en cualquiera de mis Componentes, declaro que todas las implementaciones posibles deben usar esas mismas dependencias.
¿Alguien más se ha encontrado con este problema? ¿Cuál es la forma de resolverlo como si fuera una torta?
¡Gracias!
Con el patrón de pastel, al menos con el ejemplo al que siempre voy , debe separar la definición de interfaz del componente de su implementación predeterminada. Esto separa limpiamente las dependencias de la interfaz de las dependencias de la implementación.
trait RegistryComponent {
// no dependencies
def registry: (List[Int] => List[Widget])
}
trait DefaultRegistryComponent extends RegistryComponent {
this: DatabaseComponent => // default registry depends on the database
def registry: (List[Int] => List[Widget]) = new DefaultRegistry()
class DefaultRegistry extends (List[Int] => List[Widget]) {
def apply(xs: List[Int]) = xs.map(database(_))
}
}