Caso de un modelo inusual: Elementos relacionados con la presentación.

¿Qué pasa, chavales? A pesar de que normalmente no me lee ni el Tato, ayer mantenía una “conversación” por Twitter, dentro de lo puede ser una conversación en Twitter, con @XaviPaper y con @gulnor. Era sobre un modelo un tanto raro que incluye objetos que normalmente estarían representados directamente en la interfaz gráfica de usuario y no en el modelo. En este caso, los elementos gráficos SON EL MODELO. Se presenta, pues, un problema un tanto atípico. Fundamentalmente, las relaciones son de contenedor/contenido y lo primero en lo que he pensado es en un patrón Composite que incluya a la mayoría de elementos del modelo. También es interesante el tema de que la estructura no es un árbol, sino un grafo. Algunos elementos especiales (formularios) pueden tener más de un padre.

Aquí va todo el enunciado con la descripción completa del problema.

ABSTRACTO

En mis ratos en casa estoy construyendo una especie de maquetador de formularios dinámicos. ​​​​El usuario puede añadir elementos a un formulario, arrastrarlos a diferentes posiciones, redimensionarlos y cambiar sus propiedades. Llegado el momento, el usuario decidirá “renderizar” el formulario y donde corresponda, los elementos del formulario que son dinámicos (campos) tomarán un valor “real” formulario de acuerdo con una fuente de datos (sea un XML, una tabla de una base de datos, un archivo CSV…). Esto sería algo similar a lo que hacen los Bindings en XAML.

He estado dándole vueltas al coco para hacer un modelo flexible, elegante y que “hable” por sí mismo.

He probado varias cosas y no me acaban de convencer. O bien es demasiado restrictivo o demasiado permisivo. T​e​n​g​o​ ​u​n​ ​p​a​r​ ​d​e​ ​prototipos actualmente, pero he decidido no seguir adelante porque la implementación y el modelo empiezan a no casar como me gustaría.

El dominio es un documento destinado a la presentación/impresión, con la particularidad de que habrá partes dinámicas que dependerán de una fuente de datos (por ejemplo, un XML, un archivo de texto o una base de datos).

Podéis imaginar que es un PDF que acepta una fuente de datos “externa” y renderiza el contenido dependiendo de la misma. Si dentro del documento existe un campo que hace referencia a un dato llamado “NombreUsuario”, en el proceso de renderizado, el campo tomará el valor de esa variable y en el campo podrá, por ejemplo “Pepito López”.

ESPECIFICACIONES

Un documento está compuesto de páginas. El tamaño de las páginas es un tamaño estático (ancho x alto). El usuario podrá cambiar el tamaño de la página, pero no estará enlazada con datos (al contrario que los campos dinámicos).

Una página (formulario) está compuesta otros elementos que pueden ser:

  1. Formas (rectángulos, líneas, elipses…). No tienen ningún tipo de dinamismo.
  2. Campos dinámicos: textos. Se enlazan a una variable y se reemplaza al renderizarse.
  3. Imágenes dinámicas: Imágenes. De acuerdo a un token (o ruta de acceso) se pone una imagen u otra.​
  4. Textos fijos.
  5. Elementos compuestos.
  6. Tablas
    1. Tablas cuyos elementos son generados dinámicamente: se sabe el número de columnas, pero no el número de filas.
    2. Tablas cuyos elementos (m x n) se conocen, aunque su contenido puede ser variable.
    3. COMPOSICIÓN: Podrán tener imágenes, texto, campos ¡u otras tablas! Es más puro estilo tablas embebidas de Word o HTML
  7. Gráficos estadísticos
    1. El valor de las variables es dinámico
  8. Secciones (COMPOSICIÓN). Es una especie de contenedor cuyo contenido, además de elementos de su mismo tipo, puede contener todos los anteriores.
  9. El tamaño de una sección es el tamaño que ocupen sus elementos hijo.

Todos los elementos tienen ancho y un alto. La manera de indicar la posición que ocuparán en la página es mediante coordenadas absolutas respecto al punto superior izquierdo de su contenedor.

NOTA. Una sección puede estar contenida en varias páginas o en varias secciones. Es decir, si la página 1 y la 2 comparten la misma sección y la modificamos, el cambio se reflejará en ambas páginas. A la hora de componer una sección a base de otras, la restricción será que una sección no podrá contenerse a sí misma, recursivamente.

Los elementos compuestos me vuelven un poco loco, porque son bastante dinámicos. Por ejemplo, en el caso de una tabla, no solamente las filas se generan de acuerdo a la fuente de datos, sino que los elementos de cada columna son dinámicos ​​​y además hay composición (recursivamente una celda de una tabla puede contener una tabla u otro elemento complejo).

También el tema de la composición de elementos, dudo en cómo se podría hacer.

¿Qué opináis, lobos de mar? ¿Es todo tan jodido como parece? ¿Tengo que hacerme un sistema de layout de doble pasada como el de  WPF para solucionar el tema?

Espero que opinéis😀 ¿Alguna propuesta?

Más adelante trataré de poner algún diagrama de los que he estado haciendo.

7 pensamientos en “Caso de un modelo inusual: Elementos relacionados con la presentación.

  1. Hola,

    Un par de cosas:

    Se parece sospechosamente a un sistema de generación de informes. No sé si lo haces por gusto o por necesidad, y si es parte de otra aplicación o un producto en si mismo. Si lo haces por necesidad y forma parte de otra aplicación, mi consejo es que te olvides de todo esto y uses algo como Crystal Reports o Reporting Services. Son complejos, incómodos de manejar y horribles de personalizar, pero aun así, es más sencillo que partir de cero.

    Volviendo al problema concreto que exponías en twitter en el otro día, es decir, a cómo hacer que un mismo elemento forme parte de otros dos pero en distintas posiciones, una posibilidad es que encapsules el elemento en otra clase que añada la información relativa a la relación. De esta forma, los elementos no saben nada de su posicionamiento, y es la asociación elemento-padre la que mantiene esa información. Hay varias formas de hacer esto, una de ellas sería la que aparece en este gist:

    • Ya he leído el comentario. Como le decía a Xavier, la vasta mayoría de elementos contenidos serán “hojas” (formas) que podría decirse que son value-objects. Son rectángulos, elipses, textos… incluso campos. Pero no hay continuidad ni es necesario identificarlos. Sin embargo, las secciones sí que tienen entidad y pueden estar contenidas en varios sitios. Es por eso que hablaba de un grafo dirigido acíclico. Una página puede tener una sección, y a su vez, otra sección embebida. Dentro de 2 páginas puede estar contenida la misma sección, pero no el mismo rectángulo (una forma). En el caso de las secciones, compartirían la misma instancia. ¿Por qué te digo esto? porque en el caso de las secciones sí que podría tener sentido lo que me dices. Una sección tendría distintos padres y distintas coordenadas respecto de cada uno. Pero en la vasta mayoría de los casos, los objetos solamente tienen un padre.
      A priori veo algo complejo lo que me dices. Habría que consultar un diccionario, que contendría los contenedores, sus hijos, y sus posiciones. Algo así como una tabla Look Up, ¿no?

      • Yo trataría todo de forma homogénea, me parece más sencillo. Si no, vas a acabar con un muchos ifs feos por ahí violando un montón de principios de esos que te gustan (empezando por el de Liskov y siguiendo (seguramente) por la ley de Demeter).

        De hecho, si realmente los rectángulos son Value Objects (cosa que dudo mucho), serían inmutables y, por tanto, podrías compartir sin problemas una misma referencia en distintos padres.

        • Solamente puedo decirte que llevas más razón que un santo. Respecto a lo de los value-objects, realmente no lo son, porque está claro que el usuario debería poder cambiar, por ejemplo, el color de fondo y con una acción tal, mutaría, lo cual está prohibido por definición. En realidad lo que quiero decir es que ese rectángulo podría cambiarse por otro con los mismos valores sin que repercutiese en nada más (aunque quizá sí repercutiera si a la larga se implementase un mecanismo de “Undo/Redo”). Creo que voy a probar a hacerme un modelo tal y como me dices. La homogeneidad es muy beneficiosa😀

  2. Muchas gracias por aportar tu sabiduría, Juanma🙂 Aún no lo he leído detenidamente, pero quería contestarte al tema de plantear la duda por gusto o por necesidad.

    Como bien dices, parece un sistema de generación de informes. Es similar. Mi interés por solucionar un problema complejo como el que comento está en que yo acostumbro a hacer en casa proyectos personales. Habrás visto que trabajo en una empresa actualmente (DocPath) que se dedica a la gestión documental. A priori cualquiera podría pensar “este tío tiene que hacer esto en su empresa y quiere que los demás le ayudemos a ganarse el jornal a nuestra costa”. No te culpo por pensarlo, pero soy un tipo curioso por naturaleza y mi fuente natural de aprendizaje y donde más tiempo paso programando es en el trabajo. Tenemos una aplicación HECHA que realiza justo lo que te digo. Pero cuando llego a casa siempre me planteo si se podría hacer mejor, desde cero, yo mismo y a mi estilo. En pocas palabras, hacerme una aplicación que resuelva lo básico del problema, para entrenar mi mente y para probar mis “pajas mentales” (IoC, DI, AutoMapper, AOP, mis bibliotecas…). Evidentemente, tengo libertad de hacer lo que quiera y probar. Es justo lo que quiero.

    Que sea algo parecido a una aplicación que ya existe en la empresa no es casualidad. Como te digo, es código heredado en su mayoría, hay miles de cosas que no se pueden cambiar. Simplemente, es una FUENTE DE PROBLEMAS REALES y como tal, me interesa especialmente saber cómo podría resolver problemas de diseño reales, de aplicaciones reales.

    Para que te hagas una idea, en la anterior empresa que estuve sí tenía que aplicar DDD, acceso a datos, servicios distribuidos y un montón de historias que en mi proyecto actual no tienen cabida. ¿Qué tiene que ver una aplicación totalmente de gestión con un diseñador gráfico?

    Sin embargo, sigo interesado por todo aquello, me estoy leyendo libros, el otro día me bajé el libro de N-Layer App (Microsoft España / César de la Torre) de nuevo para leerlo en cuanto acabe el de Eric Evans. Mi interés va más allá de aplicarlo para salvarme el culo. Quiero ser un buen programador y saber resolver situaciones variopintas (eso sí, JavaScript me da grima)🙂

    Espero que después del rollo que he montado no sigas pensando (ni piense nadie) que mis preguntas son para sacar tajada. Es innegable que sabiendo más uno está más preparado, y eso tiene valor, pero realmente no estoy pidiendo los apuntes a nadie ni pidiendo que me resuelvan la vida. Simplemente me encuentro un poco a la intemperie, sin nadie que me eche una mano. Solamente intento mejorar.

    Un saludo y en cuanto pueda leo con detenimiento tu comentario🙂 ¡Gracias de antemano!

  3. Hola José Manuel y Juanma,

    A ver si consigo explicarme: La propuesta que el otro día lancé era un poco demasiado ambigua al ver ahora los requisitos que se necesitan. Pero a ver si explico una nueva versión del modelo.
    http://sdrv.ms/1ch0hGn

    La idea es que un contenedor es una página, tabla o sección. Cada contenedor contendría una relación con otros contenedores a los que contiene (los bucles se deberían controlar por código) y una relación de continencia de controles.
    Tanto los contenedores como los controles tendrían el ancho y alto, pero la posición relativa serían atributos de la relación. Esto de las relaciones-clase es un concepto extraño, pero que se resuelve en BD de la siguiente forma:
    – Como la relación reflexiva de contenedores es una M:M el campo x e y estaría en la tabla intermedia.
    – Como la relación entre contenedores y controles no es M:M, estos dos campos irían en la tabla de controles
    A nivel de modelo, como se debe poner el x e y en algún sitio y no puede ser en la relación.

    La idea sería tener algo de este estilo:

    public class Continence
    {
    public int X { get; set; }
    public int Y { get; set; }
    }
    public class Continence_Container : Continence
    {
    public Container { get; set; }
    }
    public class Continence_Control : Continence
    {
    public Control { get; set; }
    }

    public class GraphicItem
    {
    public int H { get; set; }
    public int W { get; set; }
    }
    public class Container : GraphicItem
    {
    // Alternativa 1
    public List Contents { get; set; }
    public Continence_Container AddContent(Container container, int x, int y)
    {
    Contents.Add(new Continence_Container {
    Container = container,
    X = x,
    Y = y
    });
    }

    // Alternativa 2
    public Continence_ContainerList Contents { get; set; }
    // Habria que crear esta clase donde se contendrían los métodos de Add para ocultar la complejidad

    // Igual que lo anterior
    public List Controls{ get; set; }
    }
    public class control : GraphicItem
    {
    }

    • ¡Saludos a ambos! Muchas gracias por vuestros comentarios. No había podido contestar hasta ahora (¡día de compras mensuales!) y prácticamente acabo de llegar a casa y cenar🙂
      He llegado a la conclusión de que ambos me proponéis soluciones muy similares, corregidme si me equivoco. En cualquiera de los casos (también las 2 alternativas de Xavi) se encapsulan las coordenadas en una clase independiente que relaciona contenedor y contenido. Creo que puede ser acertado. Juanma mencionaba la homogeneidad a la hora de tratar los elementos. La homogeneidad es realmente simplicidad a la hora de implementar e incluso de entender el modelo. Creo le aportaría limpieza.

      Por lo pronto tengo que probar a hacer un diagrama de clases y ver cómo quedaría todo. Si os parece, voy a intentar hacerlo mañana y lo publico en un nuevo post para no recargar mucho éste.

      Gracias de nuevo🙂 ¡os aviso por Twitter!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s