Ir al contenido



Foto

Como pasan de la grilla(dataset) a un objeto de una clase


  • Por favor identifícate para responder
25 respuestas en este tema

#1 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 08 junio 2017 - 06:21

En un sistema que quiero programar con orientacion a objetos, tengo las grillas de productos y clientes etc.

 

Quisiera saber cuando se selecciona un producto para agregarlo al detalle de la factura, como pasar del dataset(el registro seleccionado) a mi objeto producto. ¿Esto tiene que ver con el mapeo ORM?

Tengo una unidad aparte con el codigo de la clase producto y un form con la grilla y unos filtros.

Lo mismo para agregar el detalle de la factura, mi detalle esta asociado a un producto en el constructor. Una vez que creo el objeto detalle se lo debo agregar al dataset de la grilla?

 

Osea yo tengo alguna idea de poo y otras cosas pero nunca me habia topado con esto..

 


  • 0

#2 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.408 mensajes
  • LocationRepública Dominicana

Escrito 08 junio 2017 - 09:41

Puedes hacer uso de Records y TList en tu clase para almacenar los detalles.


  • 0

#3 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 08 junio 2017 - 11:23

Puedes hacer uso de Records y TList en tu clase para almacenar los detalles.

Si de hecho la clase factura tiene un lista de detalles.
Pero pregunto la relacion cn los datasets..

Enviado desde mi SM-G530M mediante Tapatalk
  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.269 mensajes
  • LocationArgentina

Escrito 08 junio 2017 - 08:33

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,


  • 0

#5 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 09 junio 2017 - 06:27

Muchas gracias amigo Delphius. A mi me cuesta mucho esto porque en la universidad no me dieron tanto..simplemente conceptos algunos. Pero voy a intentar.

Saludos y gracias por tu tiempo


  • 0

#6 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 08:08

Poco para agregar a lo dicho por Marcelo.

Por lo general en las facultades se explican algunos de los patrones clásicos del libro de Gamma. Con esto ya se espera que sepan dónde buscar y como leer este tipo de información para que puedan usarse como guía en tus aplicaciones.
Después en mí Facu hay una materia que le dan un poco más de caña a esto, usan un ORM de java para conectar objetos a dominio. No sé cómo se dicta la materia, pero en 4,5 meses no creo que pueda hacerse mucho. Es para dar un pantallazo me imagino


Hay otro libro que se puede llamar el libro de patrones nivel 2, que nunca me acuerdo exactamente el nombre, pero es algo así como "Patrones de arquitecturas de aplicaciones empresariales". Ahí se detallan muchos más patrones, y la mayoría habla de bases de datos y como abstraerse de ella.

Hace poco Eliseo publicó un video de Daniele Teti en donde explica cómo implementar uno de esos patrones en Delphi, de manera súper sencilla. No es la opción más elaborada pero es mucho mejor que la clásica de Delphi

Por lo general yo uso el patrón repositorio para abstenerme de la bd; y modelo vista presentador para mostrar datos, aunque nunca me puse a analizar si realmente lo estoy aplicando de forma canónica o simplemente use los conceptos para crear mí versión
  • 0

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.269 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 10:34

En mi caso no he recibido formación académica sobre Patrones de Diseño. Los estudié por mi cuenta.

Si bien si se me enseñó UML en la facultad, no hemos ido más allá. El libro de Craig Larman (aviso de paso que me equivoqué a citar la edición en mi post. Yo me estaba refiriendo a la 2da edición... desconozco si hubo posteriores ediciones) formaba parte de la bibliografía junto a UML gota a gota. Aunque este último a mi me deja sabor a poco... una lástima viniendo de Martin Fowler, quien es muy reconocido y reputado en Ingeniería de Software.

 

Habrá muchas cosas que no estudiarás en la Uni. Deberás quemarte pestañas por tu cuenta. Pero no temas, porque la idea de la formación es justamente formar profesionales que aprendan a aprender, a re-analizar, a cuestionarse, a re-inventarse. Sobre todo en el área de las Ingenierías e Informática donde constantemente nos estamos renovando.

Yo suelo decir muy seguido que las Facultades de Ingeniería e Informáticas lo que hacen es crear Autodidactas con formación.

Y es que es así... un Informático ES un autodidacta.

 

Saludos,


  • 1

#8 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 09 junio 2017 - 11:33

Claro por eso es que quiero aprender je.

 

Y porque no se podria pasar directo del dataset que esta en un clientmodule al metodo constructor del objeto?


delphi
  1. p1.create(ClientModule1.cdsprodidproducto.AsInteger,ClientModule1.cdsprodprecio.AsFloat,ClientModule1.cdsprodiva.AsFloat,ClientModule1.cdsprodnombre.AsString, ClientModule1.cdsprodpreciofacta.AsFloat,ClientModule1.cdsprodpreciofactb.AsFloat);

Voy a probar ese codigo a ver que pasa. 


  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.269 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 03:13

Claro por eso es que quiero aprender je.

 

Y porque no se podria pasar directo del dataset que esta en un clientmodule al metodo constructor del objeto?


delphi
  1. p1.create(ClientModule1.cdsprodidproducto.AsInteger,ClientModule1.cdsprodprecio.AsFloat,ClientModule1.cdsprodiva.AsFloat,ClientModule1.cdsprodnombre.AsString, ClientModule1.cdsprodpreciofacta.AsFloat,ClientModule1.cdsprodpreciofactb.AsFloat);

Voy a probar ese codigo a ver que pasa. 

 

Más de 5 parámetros se vuelve molesto. ¡Imagínate en una clase que tenga 20 atributos!

Cuanto menos parámetros sean mejor.

Y cuantas más clases tengas, más se verá afectado.

 

Un diseño relativamente simple, y "sucio", consiste en hacer que el propio DataModule sea la fachada (que después de todo por algo está, usémoslo) y definir en éste los métodos necesarios para materializar y desmateralizar. Pero jamás el DataModule debería de crear objeto del dominio. Más bien éste los recibe ya instanciado.

La interfaz que muestre al "público" el módulo de datos estará concebida teniendo en cuenta el dominio. Por ejemplo:


delphi
  1. procedure TDataModuleFacturacion.GuardarVenta(Venta: TVenta);

podría hacer algo como esto:


php
  1. procedure TDataModuleFacturacion.GuardarVenta(Venta: TVenta);
  2. begin
  3. QueryVentas.SQL.Clear;
  4. QueryVentas.SQL := 'INSERT INTO VENTAS (...) VALUES (Venta.Monto, ...) returning ID';
  5. QueryVentas.ExecSQL;
  6. Venta.OID := QueryVentas.ParamByName('ID').AsInteger;
  7. DataModuleFacturacion.GuardarDetalleVentas(Ventas.GetDetalle, Venta.OID);

Fíjate que en el ejemplo lo que se hace es guardar una venta, obtener el ID que le asigna el motor (como es el caso del funcionamiento de Firebird) y recuperarlo para establecerle el OID. Inmediatamente se invoca al método GuardarDetalleVentas, que recibe como parámetro el detalle asociado a la venta, y el OID de la venta. ¿Porqué este OID? Lo necesitamos para asignarle el valor a la FK cuando insertemos en la tabla de detalles. Otra alternativa es que reciba directamente la clase Venta y lea todo desde ésta.

 

Este burdo ejemplo te puede evitar poner un intermediario entre el dominio y el datamodule. Ahora bien, el problema es que ahora se genera un alto acoplamiento entre el DataModule y el dominio. El acoplamiento no se puede evitar, se necesita; como ya lo dije en el post anterior.

Lo que se busca con una capa de persistencia es reducir este acoplamiento tan directo y hacer que lo asuman intermediarios.

Dominio - CapaPersistencia - Datos {DaTaModule, DataSets, etc}

 

Por ejemplo, vease esta desmaterialización (tener objeto a partir de un registro de la DB)


delphi
  1. procedure CapaPersistencia.DesmaterializarVenta(Venta: TVenta; OID: integer);
  2. var DataSet: TDataSet;
  3. begin
  4. DataModuleFacturacion.ConsultarVenta(OID, DataSet); // DataSet se obtiene por referencia
  5. if DataSet <> nil
  6. then begin
  7. Venta.OID := OID; // Le asignamos el OID ya que hemos comprobado que es válido
  8. Venta.Total := DataSet.FieldByName('Total').AsFloat;
  9. // ...
  10. end;
  11. end;

La definición ahora de ConsultarVenta podría ser algo así:


delphi
  1. procedure TDataModuleFacturacion.ConsultarVenta(OID: integer; var DataSet: TDataSet);
  2. begin
  3. QueryVenta.SQL := 'SELECT * FROM VENTAS WHERE ID = POID';
  4. QueryVenta.ParamByName('POID').AsInteger := OID;
  5.  
  6. if NOT QueryVente.IsEmpty
  7. then result := QueryVenta;
  8. end;

Podemos ver que ahora el datamodule no tiene vinculación con los objeto del dominio. Simplemente entiende tipos simples y a lo sumo interactúa con datasets.

La responsabilidad de hacer el trabajo duro la pasamos a la capa persistencia, que se encarga de "llenar" los objetos con lo que le devuelve el módulo.

 

¿Cuándo y donde podría tener lugar esta desmaterialización de la venta? Eso ya dependerá de lo que tu necesites.

 

La materialización por otra parte consistirá en "descomponer" la factura en los campos, para de esa forma poder pasárselos a un dataset con una instrucción INSERT, o UPDATE.

 

Espero que esto te sirva para ir entendiendo más algunas cosas.

 

Saludos,


  • 0

#10 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 09 junio 2017 - 03:27

Te agradezco el esfuerzo Delphius..soy novato..

Sucede que mi aplicacion consume un servicio DataSnap(ya habia desarrollado en otros post se ve), y la logica esta en el servidor, ese es el sentido de las 3 capas.

 

Ahora bien mi datamodule tiene:

 

el famoso TDSProviderConnection,

los TDataSource,

y los TClientDataSet.

 

A los dataset les asigno el TDSProviderConnection para luego asignarle el nombre de proveedor(que esta en el servidor).

 

Osea cuando la aplicacion se abre si detecta conexion los datos se cargan automaticamente. Quizas es algo muy complejo 3 capas y poo?

Deberia centrarme en algo mas sencillo quizas, pero en su momento lo empece asi.

 

Osea yo deberia cargar las grillas con un metodo de una clase, esto me recuerda el getALL por ejemplo.

 

Entonces podria ser, tener un metodo (en la clase producto,cliente,etc) en el servidor que devuelva todos los productos, en este estaria la consulta, tal como hiciste vos la consulta de ventas. Entonces el error es tener las clases en la aplicacion cliente. De hecho el cliente es una interfaz grafica, osea le erre. Las clases son parte de la logica entonces.

 

Sucede que programe con poo en php, pero nunca con delphi.

Osea la grilla de productos entonces: es un monton de objetos, instancias de la clase producto al mismo tiempo? no es pesado eso?

 

Saludos


  • 0

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.269 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 04:50

Si hay algo que nunca me voy a cansar de señalar es la confusión de conceptos de Capas y Niveles.

Son conceptos independientes, pero que muchos confunden y los asumen como sinónimos.

 

Un desarrollo de 2 capas es esto:

Capa Datos (DB) <- Capa Aplicación

 

Un desarrollo de 3 capas es esto:

Datos <-> Lógica/Dominio <-> Visualización/Presentación

 

Un desarrollo N-capas lo puedes ver en la imagen que adjunto. Fue extraido del libro UML y Patrones.

 

Por otro lado existen las aplicaciones multiniveles, o N-tier. Lo más habitual es 3-tier.

El concepto de niveles, que es para lo que fue diseñado DataSnap, es conseguir un diseño distribuído entre varios equipos. En el nivel más bajo están los clientes, que suelen ser aplicaciones delgadas que simplemente se van a comunicarse, consumir y delegar todo a la aplicación servidor que es quien hace el trabajo realmente y concentra el grueso de la lógica. Esta aplicación servidor es el 2do nivel, y es la que se comunica con el servidor de la base de datos (el 3er nivel).

El compañero Al González, que no suele verse por acá desde hace buen tiempo, popularizó en la comunidad Delphi el término "2.5-Tier" o "Nivel 2.5" para referirse cuando la aplicación servidor y el servidor de base de datos es el mismo equipo.

 

Podemos concebir una aplicación que sea ambas cosas, por supuesto.

Y nada nos impide "hacer OO" en cualquiera de estas.

 

Tu, por capas, ¿A que te refieres? ;)

 

La grilla es un componente visual. No un montón de objetos. Que le podemos "linkear" un montón de objetos es otra cosa. No debería preocuparte, en principio, la cantidad. ¿Cuantos productos estamos hablando? ¿Un millón? ¡Lo dudo!

¿Cuanto pesa una clase Factura? ¿O la clase producto? ¿300 bytes? Si tenemos 1000 productos cargados... 1000 x 300 = 292,97 KB. Poca cosa la verdad comparado a lo que "pesa" un StringGrid (que recuerdo que estaba cerca de los 1000 bytes) la verdad.

 

Una grilla es para mostrar muchos registros, pero es bien cierto que tampoco es para abusar. Una buena práctica es la de mostrar solamente lo que el usuario necesite, y esto implica mostrar registros filtrados. Sobre todo si vamos a estar en ambientes C/S, y/o N-tier para reducir uso de ancho de banda, y de red.

No es sano en absoluto cargar por ejemplo de entrada 100 millones de registros para mostrárselos cuando el usuario a lo sumo esperará el 1% de eso.

 

La materialización perezosa es una buena aliada para reducir consumos. Carga lo necesario, y si no está cargado y se requere recién pedirselo al motor para materializarlo. Por ejemplo, para una tienda de supermercados no te vas a poner a cargar en los cajeros todo... le mandas los 200 más consumidos o vendidos, ya que lo más probable es que 66% de la gente y de los casos no necesites de más. Esto lo podemos llevar para otros escenarios... ¿vas a cargarte los movimientos contables del año 1999 si estamos en el año 2017? ¡No! ¿No cierto? Pues eso: a la grilla, la info filtrada. Hay que dejárselo masticado al user.

Y algo similar se puede hacer para ir liberando "memoria". Una vez confirmada la factura, a esa clase no la vas a necesitar hasta que se disparen otros procesos de negocio. Dependiendo de las políticas de la emprea puede ser minutos o días. Algunos contadores suelen tomarse el trabajo acumulado de la semana para el viernes y hacer el control de facturación. asi que... ¿Para que mantener "viva" esa clase si ya sabemos que no la vamos a usar? ¡Liberemosla! Esto no quiere decir eliminarla de la base de datos, simplemente la quitamos.

Lo que se pueda ir liberando, libera. Y obviamente habrá cosas que mantendrás más tiempo en memoria. El listado de productos es un caso de esto último, pero como dije... podemos ir cargandolo a medida de las necesidades.

 

No se si esto te ayuda a responder tu duda.

Sobre más detalles sobre DataSnap no puedo decir ya que no he estudiado eso.

 

EDITO:

Me había olvidado de adjuntar la imagen

 

Saludos,

Archivos adjuntos


  • 0

#12 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 04:52

La grilla es [...]

 

La grilla es la esposa del grillo  :D  :D  :D  :D  :D  :D  :D  :D  :D  :D  :D


  • 0

#13 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.269 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 05:01

La grilla es la esposa del grillo  :D  :D  :D  :D  :D  :D  :D  :D  :D  :D  :D

 

:D Pos tenes razón y cuando la tenes no se gua' a discutí. 

 

Saludos,


  • 1

#14 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 10 junio 2017 - 05:23

Por capas me referia a tener la base de datos en un servidor..un servidor de aplicaciones, (o varios ya que esto permite escalabilidad) y luego la aplicacion cliente.

Lo que te decia que yo las clases e interfaces las estaba codificando en units pero en el cliente.

Lo de los registros qie explicas lo entiendo perfectamente..gracias por tu tiempo

Enviado desde mi SM-G530M mediante Tapatalk
  • 0

#15 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 10 junio 2017 - 10:24

Yo suelo hacerlo de esta manera (es un ejemplo super sencillo)

delphi
  1. uses
  2. System.Generics.Collections;
  3.  
  4. type
  5. // varios factores influencian la posibilidad de usar clase/interfaz/record.. desde tus gustos,
  6. // complejidad de la entidad, limitaciones del lenguaje. Por ej hay frameworks que
  7. // se encargan automaticamente de la serializacion, pero no son compatibles con interfaces o
  8. // lo son parcialmente, porque usan atributos o RTTI que no esta disponible para las interfaces
  9. TUser = record
  10. Name: string;
  11. Password: string;
  12. Mail: string;
  13. Details: string;
  14. end;
  15.  
  16. // podria bien ser una clase abstracta, pero la interfaz siempre es mas flexible
  17. IUserRepository = interface
  18. ['{FDD2E473-1731-428A-BF0C-F184A3483086}']
  19. // Devuelve True si el usuario existe en el repositorio; False en caso contrario
  20. function Contains(const User: TUser): Boolean;
  21. // Guarda el usuario en el repositorio; si el usuario ya existia, se sobreescriben
  22. procedure Save(const User: TUser);
  23. // Elimina el usuario del repositorio
  24. procedure Delete(const User: TUser);
  25. // Devuelve todos los usuarios del repositorio
  26. function Select: TEnumerable<TUser>;
  27. end;

 
Codigo que consume el repositorio:

delphi
  1. procedure PrintUsers(const Repository: IUserRepository);
  2. var
  3. Each: TUser;
  4. begin
  5. Writeln('Usuarios..');
  6. for Each in Repository.Select do
  7. begin
  8. Writeln('Nombre: ' + Each.Name);
  9. Writeln('Mail: ' + Each.Mail);
  10. Writeln('Observaciones: ' + Each.Details);
  11. Writeln;
  12. end;
  13. end;

Ahora, para crearlo, una posible implementacion:

delphi
  1. type
  2. TFakeUserRepository = class(TInterfacedObject, IUserRepository)
  3. strict private
  4. FUsers: TList<TUser>;
  5.  
  6. function Contains(const User: TUser): Boolean;
  7. procedure Save(const User: TUser);
  8. procedure Delete(const User: TUser);
  9. function Select: TEnumerable<TUser>;
  10. strict protected
  11. property Users: TList<TUser> read FUsers;
  12. public
  13. constructor Create;
  14. destructor Destroy; override;
  15. end;
  16.  
  17. implementation
  18.  
  19. constructor TFakeUserRepository.Create;
  20. begin
  21. inherited Create;
  22. FUsers := TList<TUser>.Create;
  23. end;
  24.  
  25. destructor TFakeUserRepository.Destroy;
  26. begin
  27. Users.Free;
  28. inherited Destroy;
  29. end;
  30.  
  31. function TFakeUserRepository.Contains(const User: TUser): Boolean;
  32. begin
  33. Result := FUsers.Contains(User);
  34. end;
  35.  
  36. procedure TFakeUserRepository.Delete(const User: TUser);
  37. begin
  38. Users.Remove(User);
  39. end;
  40.  
  41. procedure TFakeUserRepository.Save(const User: TUser);
  42. begin
  43. if Contains(User) then
  44. Delete(User);
  45. Users.Add(User);
  46. end;
  47.  
  48. function TFakeUserRepository.Select: TEnumerable<TUser>;
  49. begin
  50. Result := FUsers;
  51. end;

Y podemos instanciar todo asi...

delphi
  1. procedure PrintUsers(const Repository: IUserRepository);
  2. var
  3. Each: TUser;
  4. begin
  5. Writeln('Usuarios..');
  6. for Each in Repository.Select do
  7. begin
  8. Writeln('Nombre: ' + Each.Name);
  9. Writeln('Mail: ' + Each.Mail);
  10. Writeln('Observaciones: ' + Each.Details);
  11. Writeln;
  12. end;
  13. end;
  14.  
  15. procedure DoIt;
  16. var
  17. Repository: IUserRepository;
  18. User: TUser;
  19. begin
  20. Repository := TFakeUserRepository.Create;
  21. User.Name := 'Juan';
  22. User.Mail := 'a@b.com';
  23. User.Details := 'dueño';
  24. Repository.Save(User);
  25.  
  26. User.Name := 'Cacho';
  27. User.Mail := 'z@x.com';
  28. User.Details := 'administrativo';
  29. Repository.Save(User);
  30.  
  31. User.Name := 'Vanesa';
  32. User.Mail := '12@x34.com';
  33. User.Details := 'contable';
  34. Repository.Save(User);
  35.  
  36. PrintUsers(Repository);
  37. end;
  38.  
  39. begin
  40. try
  41. DoIt;
  42. Readln;
  43. except
  44. on E: Exception do
  45. Writeln(E.ClassName, ': ', E.Message);
  46. end;
  47. end.

Ahora si lo que queres es acceder a la base de datos lo unico que hay que hacer es cambiar la implementacion.. y como la nueva implementacion va a necesitar el DataSet, simplemente se lo das:

delphi
  1. type
  2. TUserRepository = class(TInterfacedObject, IUserRepository)
  3. private
  4. FDataSet: TDataSet;
  5. FDataSetOwner: Boolean;
  6.  
  7. function FindUser(const User: TUser; out RecIndex: Integer): Boolean;
  8. function Contains(const User: TUser): Boolean;
  9. procedure Save(const User: TUser);
  10. procedure Delete(const User: TUser);
  11. function Select: TEnumerable<TUser>;
  12. protected
  13. property DataSet: TDataSet read FDataSet;
  14. property DataSetOwner: Boolean read FDataSetOwner;
  15. public
  16. constructor Create(const DataSet: TDataSet; const DataSetOwner: Boolean);
  17. destructor Destroy; override;
  18. end;
  19.  
  20. implementation
  21.  
  22. constructor TUserRepository.Create(const DataSet: TDataSet; const DataSetOwner: Boolean);
  23. begin
  24. inherited Create;
  25. FDataSet := DataSet;
  26. FDataSetOwner := DataSetOwner;
  27. end;
  28.  
  29. destructor TUserRepository.Destroy;
  30. begin
  31. if DataSetOwner then
  32. DataSet.Free;
  33. inherited Destroy;
  34. end;
  35.  
  36. function TUserRepository.FindUser(const User: TUser; out RecIndex: Integer): Boolean;
  37. begin
  38. DataSet.DisableControls;
  39. try
  40. DataSet.First;
  41. while not DataSet.Eof do
  42. begin
  43. RecIndex := DataSet.RecNo;
  44. if DataSet.FieldByName('Name').AsString = User.Name then
  45. Exit(True);
  46.  
  47. DataSet.Next;
  48. end;
  49. RecIndex := -1;
  50. Result := False;
  51. finally
  52. DataSet.EnableControls;
  53. end;
  54. end;
  55.  
  56. function TUserRepository.Contains(const User: TUser): Boolean;
  57. var
  58. RecIndex: Integer;
  59. begin
  60. Result := FindUser(User, RecIndex);
  61. end;
  62.  
  63. procedure TUserRepository.Delete(const User: TUser);
  64. var
  65. RecIndex: Integer;
  66. begin
  67. if FindUser(User, RecIndex) then
  68. begin
  69. DataSet.RecNo := RecIndex;
  70. DataSet.Delete;
  71. end;
  72. end;
  73.  
  74. procedure TUserRepository.Save(const User: TUser);
  75. var
  76. RecIndex: Integer;
  77. begin
  78. if FindUser(User, RecIndex) then
  79. begin
  80. DataSet.RecNo := RecIndex;
  81. DataSet.Edit;
  82. end
  83. else
  84. DataSet.Append;
  85.  
  86. DataSet.FieldByName('Name').AsString := User.Name;
  87. DataSet.FieldByName('Mail').AsString := User.Mail;
  88. DataSet.FieldByName('Details').AsString := User.Details;
  89. DataSet.Post;
  90. end;
  91.  
  92. function TUserRepository.Select: TEnumerable<TUser>;
  93. var
  94. UserList: TList<TUser> absolute Result;
  95. User: TUser;
  96. begin
  97. // ojo con las fugas de memoria!! yo por lo general nunca uso colecciones de
  98. // System.Generics.Collections y uso las de Spring que son interfaces y me olvido
  99. // de los problemas.. pero esto es solo un ejemplo. El problema es que esta clase
  100. // no maneja el tiempo de vida de la lista mientras que la otra (la FakeRepository) si
  101. // ya que devuelve la variable de instancia lista que simula el repositorio
  102. UserList := TList<TUser>.Create;
  103. DataSet.DisableControls;
  104. try
  105. UserList.Capacity := DataSet.RecordCount;
  106. DataSet.First;
  107. while not DataSet.Eof do
  108. begin
  109. User.Name := DataSet.FieldByName('Name').AsString;
  110. User.Mail := DataSet.FieldByName('Mail').AsString;
  111. User.Details := DataSet.FieldByName('Details').AsString;
  112. UserList.Add(User);
  113. DataSet.Next;
  114. end;
  115. finally
  116. DataSet.EnableControls;
  117. end;
  118. end;

Obviamente el nivel de complejidad varia de acuerdo a si queres usar solo DataSets o queres combinar con Query/Command para las operaciones SQL por cuestiones de eficiencia o porque te puede resultar mas comodo. Pero la idea general es esta; a esto me refiero con el hecho de que se "esconde" que la interfaz es implementada usando un DataSet que solo la clase conoce
  • 0

#16 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 11 junio 2017 - 04:47

Osea creaste una interfaz con todos los metodos, luego una clase que la implementa, que maneja el dataset ya sea para guardar o seleccionar todos los users.

Ahora bien voy a necesitar esa estructura para cada clase de objetos, te agradezco mucho que compartas el codigo.

Gracias Agustin! 


  • 0

#17 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 11 junio 2017 - 09:17

Para lo que comentaba de obtener el producto desde el dataset faltaria un metodo para obtner uno solo...no la lista completa como lo hace el metodo select.

 

Que les parece este codigo:


delphi
  1. function TProductoRepository.getProducto(): Tproducto;
  2. var
  3. p1:RProducto;
  4. p:Tproducto;
  5. begin
  6. with p1 do
  7. begin
  8. iva:= DataSet.FieldByName('iva').AsFloat;
  9. precio:= DataSet.FieldByName('precio').AsFloat;
  10. preciob:= DataSet.FieldByName('preciofactb').AsFloat;
  11. precioa:= DataSet.FieldByName('preciofacta').AsFloat;
  12. idproducto:= DataSet.FieldByName('idproducto').AsInteger;
  13. nombre:= DataSet.FieldByName('nombre').AsString;
  14. end;
  15. p:=Tproducto.create(p1.idproducto,p1.precio,p1.iva,p1.nombre,p1.preciob,p1.precioa);
  16. Result:= p;
  17. end;

Para lo mismo adapte tu codigo de Tuser para Tproducto...luego en el formulario de listado de producto hice una prueba y lo corri con F7:


delphi
  1. procedure TFProductos.SpeedButton1Click(Sender: TObject);
  2. var
  3. p1:Tproducto;
  4. cant:Double;
  5. pr:TProductoRepository;
  6. begin
  7. if (Ecantidad.Text <> EmptyStr) then
  8. begin
  9.  
  10. cant:=StrToFloat(Ecantidad.Text);
  11. pr:=TProductoRepository.Create(ClientModule1.cdsprod,true);
  12. p1:=pr.getproducto();
  13. ShowMessage(p1.nombre);
  14.  
  15.  
  16. end
  17. else
  18. ShowMessage('ingrese cantidad');
  19.  
  20.  
  21.  
  22. end;

Y me muestra el nombre perfectamente..este producto sera un atributo de la clase detalle..junto a otros campos..Y la lista de detalle forman parte de la factura.


  • 0

#18 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 11 junio 2017 - 09:50

Si, simplemente era un ejemplo. El patrón repositorio te dice que no pongas ningún método que no necesites. De hecho es considerado una mala práctica hacer interfaces o clases genéricas para implementarlos, a mí me ha pasado que tarde o temprano tenés problemas para trabajar con relaciones complejas o cuestiones del negocio que es imposible generalizar.

Saludos
  • 0

#19 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 mensajes
  • LocationArgentina

Escrito 11 junio 2017 - 11:26

Otra cosa que creo que esta buena señalar

 

El modelo OO puro y duro que se practicaba hace 30 años tiene una falla, y es que señalaba que si uno tenia objetos sin comportamientos, estaba cometiendo un error. De hecho hay code smells como Feature Envy y Data Class que basicamente apuntan a eliminar objetos que son contenedores de datos, se les suele decir "bolsa de getters y setters". Lo cierto es que realmente este tipo de "objetos" tienen un lugar en las aplicaciones, y es que vienen muy bien para agrupar datos.

Este tipo de objetos se los conoce como "Value Objects" o "Data Transfer Objects (DTO)" (algunas referencias: wikipedia, articulo Fowler) y la idea es que son consumidos por los objetos que proveen servicios, es decir, los objetos del negocio: estos son los que hacen el trabajo interesante, calcular, comparar, actualizar, ejecutar, etc

Los DTO por lo general no implementan interfaces, ni tampoco son subclases de algo abstracto, ni es necesario escribir test de unidad para ellos, y son muy faciles de crear, por lo que tampoco hay que preocuparse por cosas como dependency injection o factories.

 

Es simplemente una manera conveniente de pasarse parametros entre procesos, en lugar de mandar los 10 (por poner un numero) campos que modela tu producto, mandas un record (u objeto, nada lo impide, pero el record es mas practico) que contiene todos los campos seteados y que el repositorio lo mande a la BD 

 

Tambien es posible que dentro de tu modelo tengas un record que utilices como DTO y un objeto producto con comportamiento, que sabe aplicar descuentos, calcular distintas estrategias de precio, manejar cuestiones como promociones u ofertas especiales, con logica "especial" a la hora de imprimir (ej: el metodo GetDescripcion elimina los caracteres que no sean ANSI porque a la impresora fiscal no le gustan; o que recorte la descripcion a 30 caracteres), etc


  • 0

#20 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 433 mensajes

Escrito 11 junio 2017 - 01:53

Otra cosa que creo que esta buena señalar

 

El modelo OO puro y duro que se practicaba hace 30 años tiene una falla, y es que señalaba que si uno tenia objetos sin comportamientos, estaba cometiendo un error. De hecho hay code smells como Feature Envy y Data Class que basicamente apuntan a eliminar objetos que son contenedores de datos, se les suele decir "bolsa de getters y setters". Lo cierto es que realmente este tipo de "objetos" tienen un lugar en las aplicaciones, y es que vienen muy bien para agrupar datos.

Este tipo de objetos se los conoce como "Value Objects" o "Data Transfer Objects (DTO)" (algunas referencias: wikipedia, articulo Fowler) y la idea es que son consumidos por los objetos que proveen servicios, es decir, los objetos del negocio: estos son los que hacen el trabajo interesante, calcular, comparar, actualizar, ejecutar, etc

Los DTO por lo general no implementan interfaces, ni tampoco son subclases de algo abstracto, ni es necesario escribir test de unidad para ellos, y son muy faciles de crear, por lo que tampoco hay que preocuparse por cosas como dependency injection o factories.

 

Es simplemente una manera conveniente de pasarse parametros entre procesos, en lugar de mandar los 10 (por poner un numero) campos que modela tu producto, mandas un record (u objeto, nada lo impide, pero el record es mas practico) que contiene todos los campos seteados y que el repositorio lo mande a la BD 

 

Tambien es posible que dentro de tu modelo tengas un record que utilices como DTO y un objeto producto con comportamiento, que sabe aplicar descuentos, calcular distintas estrategias de precio, manejar cuestiones como promociones u ofertas especiales, con logica "especial" a la hora de imprimir (ej: el metodo GetDescripcion elimina los caracteres que no sean ANSI porque a la impresora fiscal no le gustan; o que recorte la descripcion a 30 caracteres), etc

Gracias Agustin nuevamente! Ya que tengo pocos amigos me pondre a programar hoy domingo..

 

No me habia dado cuenta el detalle de que una clase contenga solo atributos, en un sistema poo todo estara dentro de una clase o de un evento, al menos eso me decian en la facu...


  • 0