Estoy buscando patrones u orientación arquitectónica para una próxima característica que estoy diseñando. Básicamente, es una función de exportación con múltiples objetivos de exportación, y estoy buscando encontrar una manera de hacerlo lo suficientemente genérico donde enchufar nuevos objetivos de exportación no requiera muchos cambios centrales. Por objetivos de exportación, simplemente me refiero a diferentes tipos de salida, ya sean PDF, presentaciones de PowerPoint, documentos de Word, RSS, etc. Tengo un conjunto de datos base, que está representado en JSON y XML. Estos datos se usan para construir imágenes (usando cualquier número o tipos de exportación [por ejemplo, PNG, JPG, GIF, etc.), gráficos, representaciones textuales, tablas y más.
Estoy tratando de encontrar una manera de abstraer todo el renderizado y el diseño en algún tipo de motor de renderizado o diseño que maneje la adición de más objetivos de exportación. Cualquier ayuda / sugerencia / recursos sobre cómo abordar esto sería muy apreciada. Gracias por adelantado.
Para una representación pictórica de lo que estoy tratando de lograr.
fuente
Respuestas:
Para mí, el camino a seguir sería interfaces y una fábrica. Uno que devuelve referencias a interfaces detrás de las cuales se pueden ocultar varias clases. Todas las clases que hacen el trabajo real deben estar registradas en Factory para que sepa qué clase instanciar dado un conjunto de parámetros.
Nota: en lugar de interfaces, también podría usar clases base abstractas, pero el inconveniente es que para los lenguajes de herencia única, lo limita a una sola clase base.
El código está en sintaxis Delphi (Pascal) ya que ese es el lenguaje con el que estoy más familiarizado.
Después de que todas las clases de implementación se registren en la fábrica, debería poder solicitar una referencia de interfaz a una instancia de dicha clase. Por ejemplo:
debería devolver una referencia de IReader a una instancia de TXMLReader; una referencia de IWriter a una instancia de TPowerPointWriter y una referencia de representación IR a una instancia de THTMLTable.
Ahora todo lo que necesita hacer el motor de renderizado es unir todo:
La interfaz IReader debe proporcionar métodos para leer los datos que necesitan los implementadores de IRepresentation para construir la representación de los datos. De manera similar, IRepresentation debería proporcionar métodos que los implementadores de IWriter necesitan para exportar la representación de datos al formato de archivo de exportación solicitado.
Asumiendo que los datos en sus archivos son de naturaleza tabular, IReader y sus interfaces de soporte podrían verse así:
Iterar sobre una mesa sería una cuestión de
Como las representaciones pueden ser imágenes, gráficos y de naturaleza textual, la representación IR probablemente tendría métodos similares a IReader para atravesar una tabla construida y tendría métodos para obtener las imágenes y gráficos, por ejemplo, como una secuencia de bytes. Correspondería a los implementadores de IWriter codificar los valores de la tabla y los bytes de imagen / gráfico según lo requiera el objetivo de exportación.
fuente
Si bien estoy de acuerdo en que se necesita más información para pensar en una arquitectura, la forma más simple de crear diferentes tipos de objetos que se comporten de la misma manera (es decir, todos generarán una salida) es usar el patrón de fábrica. Más información aquí.
fuente
Podrías terminar con algo como esto.
Las dos fábricas se basan en:
1 - para convertir el tipo de entrada (Json / XML) en una implementación concreta de cómo convertir estos datos en una imagen / gráfico
2 - Una segunda fábrica para decidir cómo renderizar la salida a un documento de Word / documento PDF
El polimorfismo utiliza una interfaz común para todos los datos renderizados. Por lo tanto, una imagen / tabla se puede mover como una interfaz fácil.
1 - Fábrica para convertir datos JSON / XML en una implementación concreta:
La Fábrica a continuación le permite convertir los Datos xml o los Datos Json al tipo concreto correcto.
Las implementaciones concretas hacen todo el trabajo pesado de convertir los datos al tipo relevante. También convierten los datos a la interfaz IConvertedData, que se utiliza para el polimorfismo.
Puede agregar estas implementaciones según sea necesario, a medida que su código se expande.
La interfaz IConvertedData le permite pasar un solo tipo a la siguiente fase: NOTA: es posible que no devuelva vacíos aquí. Podría ser por un byte [] para imágenes o un documento OpenXml para WordDocument. Ajuste según sea necesario.
Polimorfismo:
Esto se utiliza para convertir los datos al tipo de salida relevante. es decir, el renderizado a PDF para datos de imagen, puede ser diferente al renderizar datos de imagen para PowerPoint.
2 - Fábrica para decidir el formato de salida:
Cada implementación concreta expone un método común que enmascara cómo la exportación se devuelve a las implementaciones de IConvertedData
Un cliente de muestra para todo esto sería:
fuente
Resolvimos un problema similar aquí: https://ergebnisse.zensus2011.de/?locale=en Allí tenemos principalmente "tablas" y "gráficos" para exportar en diferentes formatos: pdf, excel, web. Nuestra idea era especificar que cada objeto se representara como una clase Java propia con interfaces para crear y leer esas clases. En su caso, habría 2 implementaciones para cada objeto para crear (xml, json) y 4 implementaciones para renderizar (leer).
Ejemplo: Necesitará algunas clases para las Tablas: Tabla de clase (maneja la estructura de la tabla, validación y contenido) Interfaz CreateTable (proporciona datos de tabla, celdas, espacios, contenido) Interfaz ReadTable (captadores para todos los datos)
Probablemente no necesite las interfaces (o solo una), pero creo que siempre proporciona un buen desacoplamiento especialmente útil en las pruebas.
fuente
Creo que lo que estás buscando es el patrón de Estrategia . Tiene una variedad de clases para generar los datos en el formato deseado, y simplemente elige la apropiada en tiempo de ejecución. Agregar un nuevo formato debería ser tan simple como agregar otra clase que implemente la interfaz requerida. He hecho esto a menudo en Java usando Spring para simplemente mantener un mapa de convertidores, clave por tipo de formato.
Como otros han mencionado, esto generalmente se logra haciendo que todas las clases implementen la misma interfaz (o desciendan de la misma clase base) y eligiendo la implementación a través de una fábrica.
fuente