Ir al contenido


Foto

[RESUELTO] Objetos por valor vs. Objetos por referencia; var or not var


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

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 31 octubre 2011 - 12:11

Hola muchachos,
Lejos de ser una de mis preguntas peliagudas y/o filosóficas hoy vengo con una duda que quizá más de uno no se esperaría que la dijera. Ni yo mismo caigo del asombro     


La cosa es simple.
Estoy diseñando un mini framework de persistencia. Pues bien, resulta que dentro de éste aprovecho una ligera variación del concepto de patrones que se conoce como Mapper o Conversor de Base de Datos. La idea y el objetivo de este Conversor es obtener un objeto que represente una instancia o registro particular de alguna tabla en una base de datos.


En teoría el conversor crea o genera el objeto con los datos del registro de alguna(s) tabla(s). Es decir que dada una tabla A con los campos Campo1, Campo2, ... CampoN el conversor devuelve un objeto equivalente almacenando en Campos o atributos (quizá prefieran este término a fin de no confundir con el del campo en base de datos) privados los valores leídos:




delphi
  1. Tabla: NombreTabla -> Objeto: NombreTabla
  2. ------------------    -------------------
  3. Campo1          -> FCampo1
  4. ...                  ...
  5. CampoN            -> FCampoN
  6.                       -------------------
  7.                   Método1
  8.                   ...
  9.                   MétodoN


En código sería algo como:



delphi
  1. function TConverterNombreTabla.getObject: TObject;
  2. var obj: TNombreTabla;
  3. begin
  4.   obj := TNombreTabla.create;
  5.  
  6.  
  7.   DataSet.Sql := 'select ...';
  8.   DataSet.Open;
  9.  
  10.  
  11.   obj.Campo1 := DataSet.FieldByName('Campo1').AsXXX;
  12.   // ...
  13.   obj.CampoN := DataSet.FieldByName('CampoN').AsXXX;
  14.  
  15.   result := obj;
  16. end;






Pues bien, yo en lugar de que lo cree y generar más acoplamiento y no del bueno sabiendo que en realidad se está delegando a la capa de persistencia que conozca las clases del dominio y reciba la autoridad para crearlos deseo hacer un acoplamiento más débil y dejar que el mismo dominio se encargue de crearlos y en todo caso sea la capa de persistencia que los llene.


Por ello en lugar de crearlo, quiero hacer en su lugar:




delphi
  1. procedure TConverterNombreTabla.getObject(Obj: TObject);
  2. begin
  3.   Obj.Campo := DataSet.FieldByName('Campo').AsXXX;
  4. end;




La duda que me asalta es justamente el título. ¿Debería ser este parámetro por valor o por referencia? Es decir debe ser:




delphi
  1. procedure TConverterNombreTabla.getObject(Obj: TObject);




O bien




delphi
  1. procedure TConverterNombreTabla.getObject(var Obj: TObject);




Aún sabiendo que dentro del procedimiento no hay en realidad alguna relocalización de memoria como que justifique el uso de var... Si dentro del procedimiento se van a hacer (re)asignaciones al objeto del tipo simples (salvo en algunos casos más complejos donde algunos atributos son otros objetos) ¿Merece que se pase con el var?


Esa es mi duda... parafraseando a Hamlet var or not var.


¿Que hace a un objeto que se lo declare como var?


Saludos,
  • 0

#2 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 31 octubre 2011 - 12:47

Hola Delphius,

Si dentro de ese procedimiento sólo vas a modificar propiedades de dicho objeto creo que con pasarlo por valor es suficiente. La única razón para pasarlo por referencia sería si quisieras alterar la dirección de memoria del objeto, caso de crearlo dentro de ese procedimiento, o de destruirlo y quererle asignar nil, de modo que la sentencia llamante se diera por enterada de dicha reasignación (un ejemplo lo tenemos en el procedimiento FreeAndNil de Delphi, donde el objeto se pasa por referencia).

Si quieres en esa rutina albergar la posibilidad de que el Objeto que se envía como parámetro pueda ser creado, entonces sí sería necesario pasarlo por referencia.

Saludos
  • 0

#3 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 31 octubre 2011 - 01:39

Hola andres1569,
Te voy entiendo. Y eso estaba entendiendo, pero ya ves... luego uno entra en duda.


Ahora, ¿y en los casos en que se trata de un objeto con objetos como atributos? Por ejemplo:




delphi
  1. TMiObjeto = class
  2.   FOtroObjeto: TOtroObjecto;
  3. ...




Si se diera el caso en que MiObjeto crease el objeto de la clase TOtroObjeto y necesitase recuperar y llenar dicha información. Allí si sería necesario?


Es decir, cuando se trata de un objeto propio no hay problema. Pero en casos en objetos dependientes, donde uno crea instancias de otro y se tiene que leer y almacenar información de ambos.




delphi
  1. ob1.Campo := DataSetPadre.FieldByName().AsXXX;
  2. ...
  3. Obj1.FOtroCampo.CampoOtro := DatasetHijo.FieldByName().AsXXX;




Bueno, si... estoy violando parcialmente la Ley de Demeter. Eso sólo una aproximación.


Allí si me vería obligado a hacer uso de var. Aunque estoy pensando en que si puedo desplazar la responsabilidad de crear sus objetos dependientes y garantizar de que siempre exista instancias de éstos fuera y antes de pasarlos al framework de persistencia allí me evitaría problemas. Más sabiendo que el contexto me sugiere justamente eso:




delphi
  1. Objeto.CrearObjetoDependiente;
  2. ObjetoPersistencia.LoadObject(Objeto);




Siendo CrearObjectoDependiente un método, que entre otras cosas, tiene algo como ésto:




delphi
  1. FOtroObjeto := TOtroObjecto.Create;




¿Alguna orientación?


Saludos,
  • 0

#4 cadetill

cadetill

    Advanced Member

  • Moderadores
  • PipPipPip
  • 994 mensajes
  • LocationEspaña

Escrito 01 noviembre 2011 - 02:04

Y yo que creía que los objetos siempre se pasan por referencia y nunca por valor :D

No obstante, creo que antes de pasar cualquier objeto, ya sea por referencia o valor, el framework debería de crear el objeto en sí y los objetos dependientes de él (en el construtor del objeto padre por ejemplo y destruirlo en el destructor).


  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 01 noviembre 2011 - 04:43

Y yo que creía que los objetos siempre se pasan por referencia y nunca por valor...

Bueno en realidad es así. Los objetos de la VCL son representados por punteros con lo que al pasarlos como parámetros estas pasando su puntero, teniendo acceso, con esto, a todo el objeto.

Cuando se pasan por referencia, se está pasando la referencia a un puntero, es decir un puntero a un puntero y como apunta andres1569, permite, entre otras cosas asignarle nil.

Saludos.

  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 01 noviembre 2011 - 08:21


No obstante, creo que antes de pasar cualquier objeto, ya sea por referencia o valor, el framework debería de crear el objeto en sí y los objetos dependientes de él (en el construtor del objeto padre por ejemplo y destruirlo en el destructor).

Entonces, tu dices que lo mejor sería que el Framework de Persistencia sea quien cree el objeto que actúa de objeto persistente?
Yo más que nada quería evitar justamente esto por tres motivos:
1) Cuando se desplaza alguna autoridad de un objeto y se hace un salto de representación para llevarla a otra clase perteneciente a otra capa, y por tanto, a un contexto diferente se está violando algunos intereses de los propios objetos intervinientes y uno de los pilares de un buen diseño: "No mezcles funcionalidades". Y este es un llamado a la atención que debería pensarse (*)


(*) Bueno, tengo que reconocer que esto es relativo y que no siempre es un peligro hacer estos saltos de representación y contexto. Máxime sabiendo que la enorme mayoría de los patrones justamente hacen ésto y cambian de un experto de información a un experto de información parcial o dirijido por datos incorporando indirección, y fabricaciones puras.


2) Relacionado con (1) resulta ser que muchas de las clases del dominio ya tienen sus respectivos "padres", y son éstos los principales Expertos en Información y lo deseable es que se mantuviera lo menos posible invadido al dominio de una dualidad de quien debe asumir la autoridad de creación.


3) No sólo resulta ser que ya existe un dominio mediana y casi fuertemente estudiado sino que por naturaleza del problema y del contexto cerca de la mitad de las clases de éste son Singletons: Es decir instancias únicas, por lo que se crean una única vez en toda la ejecución del programa. Y resulta, más bien, que en todo caso se deben modificar algunos valores de sus atributos.


Entiendo lo que comentas que sea el objeto "padre" quien deba crear a su objetos dependientes. Sobre todo en las composiciones (que ocupan un poco menos de la otra mitad de las clases). En este caso es donde estoy analizando posibilidades para llevar a cabo una buena comunicación entre Conversor y los Objetos persistentes.
En teoría existe un conversor por cada clase de objeto persistente, pero esto abre un frente de discusión:


1) El Conversor padre delega en Conversor hijo el trabajo que éste sabe hacer. De ser así entonces siguiendo el mismo principio, si el Conversor padre crea los objetos padre, el Conversor hijo crea los objetos hijos. Se rompe aquí el principio de que sea el propio objeto padre quien sus dependientes.
Este planteo me lleva a la pregunta, ¿y ahora cómo vinculo el objeto hijo creado con el padre? ¿Algo como ésto?




delphi
  1. function TConverterNombrePadre.getObject;
  2. var objPadre: TNombrePadre;
  3. begin
  4.   objPadre := TNombrePadre.create;
  5.  
  6.   DataSet.Sql := 'select from Padre... ';
  7.   DataSet.Open;
  8.  
  9.  
  10.   ObjPadre.Campo := DataSet.FieldByName('Campo').AsXXX;
  11.   Objpadre.ObjetoHijo := FachadaPersistencia.GetConversor(NombrePadre).getObject(ObjPadre.OID);
  12.  
  13.   result := objPadre;
  14. end;
  15.  
  16.  
  17. function TConverterNombreHijo.getObject(OID: TOID);
  18. var objHijo: TNombreHijo;
  19. begin
  20.     ....
  21.     DataSet.Sql := 'select ... from Hijo where FK = OID ... ';
  22.     DataSet.Open;
  23.     ...
  24.     result := ObjHijo;
  25. end;




2) El propio objeto padre crea sus objetos dependientes. De ser así el conversor padre adquiere una lógica un tanto más ofuscada y complicada puesto que el Conversor padre deberá controlar y tener referencia de sus conversores hijos y no delegarselo a la Capa de Persistencia quien es la candidata más fuerte para administrarlos.
Visto desde afuera, siguiendo esta línea de pensamiento un Conversor adquiere el comportamiento abstracto y aparece la figura del patrón Composite, que justamente permite tratar de forma transparente e indistinta a un grupo de Conversores de uno solo.
Si seguimos el planteo de (2) entonces los Conversores actúan de meros llenadores de datos y sería válido el planteo inicial que les presentaba.


En fin, estoy analizado como enfocar esto y todavía no me cierra del todo las ideas; aunque me inclina y me gusta más la idea de que el Conversor llene y no que cree. No me gustaría tener que tocar demasiado el dominio, que dentro de todo ya lo tengo más estable.


Se escuchan sugerencias.



Bueno en realidad es así. Los objetos de la VCL son representados por punteros con lo que al pasarlos como parámetros estas pasando su puntero, teniendo acceso, con esto, a todo el objeto.

Cuando se pasan por referencia, se está pasando la referencia a un puntero, es decir un puntero a un puntero y como apunta andres1569, permite, entre otras cosas asignarle nil.

Saludos.

Precisamente esto de que en realidad se pasa el puntero y no el objeto en si mismo es lo que me alimentó mi duda de si debía o no ir var.


Saludos,
  • 0

#7 mightydragon_lord

mightydragon_lord

    Advanced Member

  • Miembros
  • PipPipPip
  • 73 mensajes

Escrito 01 noviembre 2011 - 10:56

Hola Delphuis, pues como sabes es un tema complicado y cada uno de tiene un punto de vista diferente. Pienso que los Objetos por referencia son la mejor opción, pero si deseas que este framework también sea de tipo Cliente Servidor y envíe mensajes a los clientes por socket o http o cualquier otro método, olvídate de ellos y lo segundo me agrada mas, un servidor de procesos que envíe como respuesta objetos serializados =P, así mantienes un manejo homogéneo de los objetos, bueno esta es mi humilde opinión, saludos.
  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 01 noviembre 2011 - 09:14

Hola Delphuis, pues como sabes es un tema complicado y cada uno de tiene un punto de vista diferente. Pienso que los Objetos por referencia son la mejor opción, pero si deseas que este framework también sea de tipo Cliente Servidor y envíe mensajes a los clientes por socket o http o cualquier otro método, olvídate de ellos y lo segundo me agrada mas, un servidor de procesos que envíe como respuesta objetos serializados =P, así mantienes un manejo homogéneo de los objetos, bueno esta es mi humilde opinión, saludos.

Se que el tema merece su amplio análisis y debate, y muchas cosas más por considerarse.
El punto es que mi objetivo no es crear un verdadero, y complejo, framework de persistencia. Por algo al comienzo del hilo mencioné mini framework.


El objetivo es proponer algunas ideas básicas de como encarar un framework elemental, y a modo prototipo. Evidentemente para algo final, a como debiera ser, hay muchas cosas que se escapan al fin de este hilo.


Centrando en cosas simples ¿Tu como lo ves?


Saludos,
  • 0

#9 mightydragon_lord

mightydragon_lord

    Advanced Member

  • Miembros
  • PipPipPip
  • 73 mensajes

Escrito 02 noviembre 2011 - 08:07

Hola Delphius, en lo personal, a mi me agrada mas y por mucho el paso de Objetos por valores, al puro estilo Soap, recibe objetos y entrega objetos, tal vez por capricho o por que no he tenido las mejores experiencias con los objetos por referencia, pero me parece en lo personal, mas eficiente y limpio usar esta técnica.
  • 0

#10 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 02 noviembre 2011 - 08:53

Personalmente veo peligroso pasar objetos como vars. Puede ser practico no tener que crear los objetos "desde fuera" y poder reasignarlos a nil, pero creo que si puedes pasar sin esto, te ahorrarias dolores de cabeza normales en programacion en C y sus dichosos punteros.

Creo que es mas sano crear las cosas "conscientemente", piensa solo que si pasas un objeto como var y lo haces nil, nada te dice que no lo tengas referenciado de antes dentro de otro objeto y se te quede ese puntero apuntando a nosesabedonde.

Claro que igual no te afecta, pero el dia que te falle por aqui no vas a saber de donde te llueven los problemas, y total para ahorrarte unas lineas de codigo.
  • 0

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 02 noviembre 2011 - 11:46

Hola muchachos,
Les agradezco su contribución y experiencia. Al igual que ustedes yo también me siento más cómodo y seguro de no pasar los objetos a la capa de persistencia por referencia y dejar que esta haga el trabajo "sucio" de crear y eliminar objetos de la capa de dominio.
Y ahora que lo analizo con mayor detalle, con razón mi material de consulta justamente más adelante aborda y amplia el concepto del framework  y sus vínculos con los objetos persistencias mediante Proxys.  :)


Yo no quiero llegar a eso, me parece aparatoso tener que poner Proxys, una Estrategia y una máquina de estados. Para lo que busco debe ser simple, y cobra más fuerza ahora disponer de una Capa de Persistencia, mediante una Fachada, off corse, que tenga métodos como:




delphi
  1. SaveProject(Obj: TProject);
  2. LoadProject(Obj: TProject);
  3. ...


Y demás SaveXXX y LoadXXX cuantos Casos de Uso tengo. Los objetos ya son creados y directamente esta fachada con sus objetos subordinados se encargue de llenar los objetos leyendo desde un DataSet, como así de modificar y guardar la información leyendo desde los objetos y pasando los valores a los DataSet para ejecutar sentencias insert o update según sea el caso.


Para lograr una sincronización y comunicación bi-direccional aprovecho el poder de los eventos de Firebird y del patrón Observer. La explicación es simple, esta Fachada además actúa de Sujeto, con lo cual aquellos objetos interesados (que son Fachadas de Caso de Uso) se suscriben y actúan de Observadores e implementan cada uno sus respuestas a los eventos que le notifican. Simplemente la capa de persistencia captura el evento disparado por Firebird y genera una respuesta a modo de evento que los observadores entiendan.


Creo que estoy encontrando mis respuestas a años de romperme la cabeza pensando en una mejor separación y puesta en práctica de los conceptos del modelo basado en Capas (Patrón Layers) y logrando separar interfaz, lógica y datos y el dilema de porqué nunca me sentí tan cómodo con el uso del data-ware.  :)  ¡Que emoción! ¡Siento que se viene un Delphius 2.0, o si lo prefieren y le gusta el lado cómico... un Pokémon evolucionando  :D  !


@Rolphy si lees esto quizá te interese, que se bien que tu también te encontraste con este mismo dilema y nos chocamos con similares paredes. Si no te molesta la demora en un mes, mes y medio (no creo que antes) pueda elaborar algún documento con mi propuesta, que aunque sea elemental estimo que ofrece un adecuado equilibrio entre simpleza y flexibilidad... Un punto medio entre lo básico y lo complejo y que quizá pueda extenderse y generalizarse para poder sacarle más provecho y reutilizarlo.


Muchachos... ¡me convencieron!  (y) 


Saludos,
  • 0

#12 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.469 mensajes
  • LocationMéxico

Escrito 02 noviembre 2011 - 12:24

¡Caramba!, estos hilos son los que me recuerdan lo poco que sé de estos menesteres, pero bueno, el consuelo es que soy bueno para tener el Bar-Tolo lleno de bebidas :D :D :D

Salud OS
  • 0

#13 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 02 noviembre 2011 - 12:37

¡Caramba!, estos hilos son los que me recuerdan lo poco que sé de estos menesteres, pero bueno, el consuelo es que soy bueno para tener el Bar-Tolo lleno de bebidas 

Salud OS

No me vengas con ésas, Egostar, te recuerdo que pasaste el Delphi Developer Certification de Embarcadero,  :D :p , y no precisamente por tu habilidad haciendo mojitos  *-)
  • 0

#14 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.469 mensajes
  • LocationMéxico

Escrito 02 noviembre 2011 - 12:48

jajajaja, pero es el basico amigo, no me animé a hacer el Master :) ahí si me regresan a la primaria a estudiar de nuevo :D :D :D

Salud OS
  • 0

#15 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 03 noviembre 2011 - 04:26

Hola a todos,
Si ya decía yo que todo parecía estar bonito... Siempre sale el tío Murphy a hacer de las suyas. ¡Y menos mal que no me pasa lo de Fred:D


Pues ni modo, he detectado algunos casos menores en los que va a ser necesario hacer que los objetos se pasen con referencia. Por tanto tendré casos como éstos:




delphi
  1. procedure TPersistentFachade.Save(Obj: TObject); overload;
  2. procedure TPersistentFachade.Save(Obj: TObject; ...); overload;
  3. procedure TPersistentFachade.Load(Obj: TObject); overload;
  4. procedure TPersistentFachade.Load(var Obj: TObject); overload;
  5. procedure TPersistentFachade.Load(var Obj: TObject; ...); overload;




Junto con algunos casos en que se necesita de un(os) parámetro(s) extra.


Y deberé controlar y asegurar que se mantenga la integridad tanto antes como después de proceder con los casos de paso por referencia. De todas formas está resultado un problema menor de lo que imaginé.


Saludos,
  • 0




IP.Board spam blocked by CleanTalk.