La mejor manera de agregar y extender un rasgo de escritor genérico para el almacenamiento de datos paso a paso

Necesito almacenar/escribir información en diferentes y múltiples soportes de datos en mi programa (H2, SQLite,NEO4J, Text, etc.) Creo el exportador uno por uno, por lo que la interfaz debe ser fácilmente ampliable.

Hago un programa de simulación, por lo que necesito almacenar información en cada paso (necesito el objeto db y los diversos datos para almacenar).

Mi primera idea es usar un patrón de inyección de dependencia con un método genérico initializeWriter(), stepWriter(), closeWriter() de esta manera:

trait OutputWriter {  Simulation =>

  def path:String

  def initializeWriter()
  def stepWriter()
  def closeWriter()
}

El usuario que necesita implementar el escritor extiende este rasgo y anula el método. Normalmente, 'database val' contiene el objeto DB después de la inicialización por initializeWriter. El objeto de datos contiene todos los datos posibles que quiero escribir.

Tengo una dependencia entre OutputWriter y Simulation, porque en realidad necesito acceder al objeto y la función desde la simulación para escribir el resultado final. Creo que no es una solución realmente buena, y dada la respuesta de @ x3ro, es una mejor solución para eliminar estas dependencias para brindar un objeto de DATOS genérico mejor al escritor.

Mi problema es en este caso si necesito agregar información compleja para escribir estos DATOS correctamente en la base de datos, ¿dónde puedo poner este código específico que contiene, por ejemplo, sqlquery u otro comando de escritor específico?

Entonces, en este punto, mi problema es:

1: creo un objeto de datos que contiene datos para escribir en cada paso de mi simulación. La estructura de este objeto de datos es la misma, solo se mueve el valor del resultado.

2 - Cualquier escritor de salida o salida múltiple, que contiene todos los métodos para escribir correctamente en la salida: TextWriter, SQLITEWriter, etc.

3 - Una parte específica del código que establece la relación entre (1) -> (2). No hay problema para escribir un indexedSeq de double en un archivo de texto, pero en una base de datos SQL, necesito darle al escritor la estructura de la tabla, la consulta para insertar datos, etc.

Mi primera idea es configurar este código en stepWriter() del método Writer, pero tal vez haya una mejor solución para esto porque creo que aquí rompo la genericidad del escritor

object DB
object MyData

trait DBWriter extends OutputWriter {

 val database:DB = DB

 def initializeWriter() = { ... }
 def stepWriter(dataToWrite:MyData) = { ... }

 } 

Después de eso, si necesito exportar mi simulación, agrego el buen escritor así:

new Simulation (...) with DBWriter {
  override def path = "my-path-for-db"

  initializeWriter()

  // computation loop 
 (0 until 50){ var result:MyData= computeData() ; stepWriter(result) }

  closeWriter()

}

¿Hay otro patrón (en la literatura o basado en su experiencia) que usa regularmente que es más robusto o más flexible para hacer eso?

Muchas gracias por tu experiencia de regreso, SR.

preguntado el 27 de julio de 12 a las 16:07

1 Respuestas

No creo que lo que presenta en su ejemplo caiga en la categoría de "Inyección de dependencia" (DI). Esto se debe a que DI tiene como objetivo reducir las dependencias en su base de código, y sus clases/rasgos en realidad dependen unos de otros:

Te sugiero que leas este artículo sobre inyección de dependencia si quieres aprender más al respecto.


Posibles problemas

En cuanto a su ejemplo, el problema es que su simulación depende de DBWriter, y necesitaría cambiar su implementación para usar otro escritor. La simulación sólo debe depender de la OutputWriter rasgo.

Otro problema parece ser que el stepWriter() método de su DBWriter La implementación necesita parámetros específicos para el escritor de la base de datos y, por lo tanto, no es genérica (y no se ajusta al rasgo OutputWriter en absoluto).

Un tercer problema es el hecho de que su OutputWriter El rasgo en realidad depende de la simulación, por lo que realmente no puedo encontrar una razón. para mantener su OutputWriter tan genérico y reutilizable como sea posible, no debería hacerlo dependiente del Simulation. ¿Cuál fue su razón para agregar esta dependencia?


Mi acercamiento

Haría los siguientes cambios para mejorar su situación de dependencia.

Hacer que OutputWriter y DBWriter sean genéricos: Tu OutputWriter realmente debería ser solo una interfaz, así:

trait OutputWriter {
    def initializeWriter()
    def stepWriter(dataToWrite:Data)
    def closeWriter()
}

Convierta DBWriter en una clase real:

class DBWriter(database:DB) extends OutputWriter {
    def initializeWriter() { ... }
    def stepWriter(dataToWrite:Data) { ... }
    def closeWriter() { ... }
}

Pasar un escritor a la simulación al crear una instancia:

object Foo extends App {
    val database:DB = ...
    val writer:OutputWriter = new DBWriter(database)

    new Simulation(..., writer)
}

De esta manera, podrá simplemente cambiar el escritor que se está pasando al Simulation constructor, ¡o incluso usar varios escritores al mismo tiempo! Esto también permite que el escritor sea configurable a través de un archivo de configuración externo (consulte el artículo sobre inyección de dependencia mencionado anteriormente).


Para abordar las preguntas en su edición

1 - Datos que quiero escribir en cada paso de mi simulación. La estructura de estos datos es la misma, solo se mueve el valor del resultado. Por ejemplo, el paso de simulación devuelve el mismo indexedSeq[Double] que contiene diferentes valores para escribir en la salida en cada paso.

2 - Cualquier escritor de salida o salida múltiple, que contiene todos los métodos para escribir correctamente en la salida: TextWriter, SQLITEWriter, etc.

3 - Una parte específica del código que establece la relación entre (1) -> (2). No hay problema para escribir un indexedSeq de double en un archivo de texto, pero en una base de datos SQL, necesito darle al escritor la estructura de la tabla, la consulta para insertar datos, etc.

Poner la lógica SQL en su DBWriter está totalmente bien (que luego llamaría SimulationSQLWriter). Por supuesto, esto hace que la implementación real sea menos genérica, pero esto no debe ser algo malo. Mantenga siempre su código lo más genérico posible, pero tan específico como sea necesario, de modo que no tenga que agregar mucha complejidad para hacer que el código sea genérico y no necesite ser genérico en absoluto.

¡Mantén siempre una proporción razonable entre tiempo y esfuerzo y beneficio!

Ahora, para un ejemplo concreto de la SimulationSQLWriter:

class SimulationSQLWriter(database:DB, table:String) extends OutputWriter {
    def initialize() { ... }
    def stepWriter(dataToWrite:Data) {
        val preparedData = prepareDataForInsertion(dataToWrite)
        insertIntoDatabase(preparedData)
    }
    def closeWriter() { ... }

    def prepareDataForInsertion(dataToWrite:Data) = { ... }
    def insertIntoDatabase(preparedData:<whatever type you need>) = { ... }
}

prepareDataForInsertion podría preparar los datos proporcionados y ponerlos, por ejemplo, en un mapa que asigna el nombre del campo SQL al valor, o una lista de tales elementos del mapa. Ese resultado podría luego arrojarse a insertIntoDatabase, que según los parámetros pasados ​​al constructor lo insertaría en el database mesa table.

Respondido el 20 de junio de 20 a las 10:06

Gracias por esta respuesta, agrego algunos detalles sobre el motivo de la dependencia entre el escritor y la simulación. Quizá ahora esté más claro. - reyman64

Traté de abordar sus problemas :) - freskoma

OK, lo entiendo mejor, muchas gracias por esta gran respuesta :) - reyman64

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.