Veamos, partamos del hecho de un hipotético pseudo-diagrama de clases básico, que más o menos, su aproximación sería así:
TFactura <>------- 0..* TDetalle
Manifestando una relación de composición. Tu verás ya luego que tipo de composición. A Efectos prácticos esto se va a traducir en que la clase TFactura contendrá un listado de instancias de la clase TDetalle. Hay dos maneras, "de libro", en como encararlo:
Manera 1: Que TFactura herede de alguna clase "List"
Manera 2: Que la clase TFactura tenga como atributo un objeto de alguna clase "List" y delegue en este que mantenga la lista.
Cada opción tiene sus ventajas y desventajas. Tu seguramente ya analizaste tus casos.
Independiente de la opción que hayas elegido, lo importante es que se mantenga la estructura de composición.
Ahora, tu dilema es que tienes un diagrama de dominio, y por el otro la capa encargada de los datos. En el medio, hay una magia que debe encargarse de mantener coherencia:
Dominio <-----> {nube mágica} <-----> Datos
La capa de Datos estará formada, por lo menos, con los TDataSet, TDataSource, TDataModule, y demás elementos asociados a estos.
La nube mágica, en parte está formada por la capa dedicada a los aspectos visuales, y otras capas (o sub-capas) de soporte.
Es bien cierto que una manera absolutamente profesional, y compleja sobre todo si uno no está muy sólido en los conceptos OO, es con una suite/biblioteca/framework ORM. Un ORM es justamente una capa de Soporte para hacer en cierta forma más "llevadero" la capa Lógica.
Ahora bien, no necesariamente debemos llegar a a esos extremo. Hay otras alternativas, y como todo... hay muchos dependes, y cada cosa tiene sus ventajas y desventajas.
Voy a centrarme en la comunicación Presentación --> Dominio.
Hay maneras "sucias" de trabajar, y hacer que esta nube sea muy finita, y también hay opciones un poco más elaboradas.
Entre las opciones elaboradas la forma clásica, y quizá la más rápida, económica, y relativamente estable y segura, es la de tener clases "link". Estas clases link lo que hace es asociar un control visual con el objeto en cuestión. En forma resumida las clases "link" son un mediador (una implementación eficaz, y profesional lo encararía por el patrón Proxy) entre el control visual y el objeto. ¿Porqué esta mediación? Pues... principalmente por 2 motivos:
1. Son dos mundos distintos... De un lado el mundo del dominio, y del otro la capa de Presentación.
2. Y además, sus tiempos de vida y usos no son los mismos. La capa Dominio tiende a tener un tiempo de vida largo... se estará trabajando sobre y con esta hasta que se finalize la aplicación. Mientras que la capa presentación es más volátil.... Podemos crear objetos visuales y estar liberando cada 2 por 3. Y si hiciéramos una relación tan directa si no somos precavidos podríamos llegar a un diseño en el que nuestros objetos dominio mueran o apunten a una dirección de memoria inválida y perderíamos refencias.
Algo de esto lo hemos discutivo, te puedo recomendar que leas estos dos hilos:
Hilo 1
Hilo 2
Creo que leyendo estos dos te puedes inspirar un poco. Ahora, la comunicación inversa, es decir Dominio -> Presentación puede encararse como un Observador. Es decir, los objetos visuales se "suscriben" a los objetos del dominio en cuestión. Cuando el objeto del dominio cambia, y si hay algo que sea necesario o consideramos oportuno y pertinente avisar a alguien, simplemente recorre la lista de observadores y les va notificando. Puedes ver una implementación de este patrón en este hilo.
Te dejo de tarea, puesto que deberás revisar todo esto ya encarado a tu diseño, como "combinar" ambas direcciones.
Ahora, también tenemos otras 2 comunicaciones: Dominio <-> Dato
Esta parte es la que puntualmente encara el mundo ORM. Y nuevamente, repito, hay maneras de hacerlo... algunas más "sucias" y otras más elaboradas. Lo que si he notado es que hay cierta tendencia a que cada desarrollador termina haciendo su propio "mini framework de ORM" Si quieres adentrarte con los ORM, hacia una mirada más profesional este artículo es quizá lo primero que deberías leer.
Y en el libro de UML y Patrones de Craig Larman tiene sus capítulos dedicados introductorios y da un par de "tips" y formas de como concebir un diseño elemental como para pensar en un "mini ORM" propio. Al menos en la 4ta edición, en sus capítulos finales (30 en adelante sino me falla la memoria).
Sea la forma en lo que encares, lo que se busca es poner una mediación... un juego de clases, ya sea por medio de una Fachada (que sería lo recomendable) o no, que interactúe y se encargue de esa comunicación. Básicamente se encarga de "traducir" recordsets o conjunto de datos en objetos, y objetos en conjunto de datos.
Técnicamente para la traducción recordset -> objeto, lo que tenemos que hacer es volcar los datos en el objeto creado. "Escribir" en la clase diría un criollo.
Y para la comunicación inversa, "leemos" al objeto y pasamos sus datos hacia alguna instrucción SQL que luego derivará en alguna operación sobre la base de datos.
Para concebir esta comunicación de manera efectiva, la mediación debe conocer ambas estructuras... es decir al dominio como la capa de datos... En términos, abstractos, y sin demasiada pérdida de generalibilidad, podríamos decir que existe una correspondencia lineal 1-1. Es decir, que si tenemos una clase A, es de esperar una hipotética tabla A con campos que referencian o que hacen alusión a los atributos/propiedades/campos de clase A. En la práctica vamos a observar que no es tan lineal, porque cuando ponemos una lupa vamos a ver casos como en los que la info se encuentra "dispersa".
Ejemplo 1. Una tabla C con n campos, pero que a nivel de dominio tenemos que la info está segmentada en m1 y m2 atributos para la clase B1 y B2 y que éstas son una clase delegadas de la clase A:
ClaseB1 -{rol}--- ClaseA -{rol}--- ClaseB2
TablaC = Atributos(ClaseB1) + Atributos(ClaseB2)
O que incluso no existiese esta clase A, y tenemos cierta independencia entre las clases B.
Ejemplo 2: A su inversa, tenemos una clase A pero que a nivel de base de datos para reconstruir el objeto debemos leer el contenido de 2 o más tablas.
Entre el ejemplo 1 y el ejemplo 2 podemos tener un buen mix.
Es más casi me animaría a decir que todo mapeo objeto <-> tabla se construye con combinaciones de los 2 casos o formas.
Habrá circunstancias en donde una clase no merezca aparecer en la base de datos... Por ejemplo: hagamos de cuenta de que hemos optado por llevar la composición entre TFactura y TDetalle delegandolo en un TObjectList<> interno (atributo privado de TFactura). Es bastante probable que veamos las tablas TablaFacturas, y TablaDetalle pero ninguna TablaObjectList. La correspondencia (1,m) a nivel de datos la conseguimos mediante la relación entre las claves primarias y foráneas. Pero a nivel OO necesitamos de un objeto que se encargue de manejar el listado.
Y también habrá casos en donde a nivel de datos hay tablas, pero que no solemos ver alguna clase que se corresponda de manera explícita.
Hay un caso especial, y que merece poner mucho cuidado.... me refiero a los escenarios donde vemos una relación (m,m). En la base de datos lo tenemos solucionado y lo hacemos visible mediante una tabla intermedia: Tabla1 -1---M- TablaIntermedia -M---1- Tabla2
Pero resulta ser que en el modelo del dominio, cuesta apreciarlo. En el mundo OO, cuando tenemos un juego de roles en donde las cardinalidades hacen este juego "mucho a mucho" lo más probable es que estemos en un caso de Clase de Asociación, y no todos lo manifiestan o la ponen en sus análisis. Luego les cuesta entender porqué tienen dificultades para determinar que campos asociar con los atributos de las clases 1 y/o 2. Lo que añade complejidad extra es que cuando se hace explícita la clase Asociación la pregunta que aparece es ¿Y a ésta quien la crea?
Ahora bien.
La pregunta que deberás hacerte ahora tu es si quieres llegar a esto... como he dicho, hay opciones, distintas maneras... y posiblemente cada quien tenga su estilo. No hay una receta única.
Puedes hacer un diseño relajado en donde tengas una clase Facha de persistencia que concentre tantos métodos Materializar(Objeto) como objetos persistentes tengas, como asi también su contraparte Desmaterializar(). La complejidad estará en que tan "anidado" esté tu Dominio, porque habrá casos en los que posiblemente tengas materializaciones perezosas y otros en los que sean inmediatas... y esto llevado a clases tan complejas donde tengas cosas como:
ClaseA <>---- ClaseB <>---- ClaseC -----> ClaseD
Y el "arbol" OO se extienda más, un Materializar(ClaseA) puede llevarte a ramas muy profundas. Esto es INEVITABLE, pero deberás controlarlo. En ocasiones se podrá aplicar la máxima "divide y vencerás" e ir haciendolo por partes según sea necesario (algo de perezosa) y en otras deberás hacer más trabajo. Por ejemplo, basado en tu ejemplo:
MaterializarFactura(Factura) vs MaterializarFactura(Factura) + MaterializarDetalleDeFactura(Factura) vs MaterializarFactura(Factura) + MaterializarDetalle(Detalle)
Cuanto más profundo necesite conocer e interactuar con las diferentes clases del dominio más acoplamiento tendrás.
Tu clase Fachada de persistencia se encargaría de interactuar con los datasets en su momento. Un Materializar() implica hacer una instrucción SELECT, y un Desmaterializar() un UPDATE o INSERT. El truco está en como, y donde, vamos a tener el famoso OID (Identificador de Objeto). El principio elemental sugiere que cada clase persistente tenga este atributo... ahora bien... hay quienes no les gusta que sus objetos del dominio "luzcan" como persistentes. Una alternativa es Proxy, y ahora tendremos que para una clase dominio habrá algún proxy y ponemos en este proxy el OID.
Creo que con esto algunas luces se te prenderán... y otras se apagarán. Yo ya te di la pelota. Ahora juega.
Saludos,