Para quienes estén acostumbrados a la Programación Orientada a Objetos les será común la expresión de que los objetos se manipulan por referencias a los mismos. La mejor manera de explicar este concepto es con un ejemplo sencillo.
Imaginemos el siguiente código:
obj1 = CREATEOBJECT("Custom")
obj2 = obj1
obj2.Comment = "Un valor cualquiera"
obj3 = obj2
? obj3.Comment
? obj1.Comment
¿Cuántos objetos de tipo (clase) Custom existen en memoria durante este fragmento de código?¿Respondió 3: obj1, obj2 y obj3? Lo lamento. La respuesta es incorrecta. Existe solo un objeto Custom (el creado con CREATEOBJECT). Obj1, obj2 y obj3 son 3 referencias al mismo objeto Custom.
Por lo tanto, ¿cuál sería la salida por pantalla de este código? Se imprimirían 2 líneas con la cadena de caracteres "Un valor cualquiera". Si no me cree a esta altura, lo invito a que haga la prueba. Esto es así porque, como ya dijimos, el objeto es único. Se modificó el estado del objeto mediante la referencia obj2 (cuando se le asignó un valor a Comment). Luego, con cualquiera de las otras referencias, se verá el mismo valor, ya que apuntan al mismo objeto. El tema de las referencias es similar a utilizar apodos. La persona es única, pero puede responder a su nombre real o a alguno de sus apodos.
Por un lado, el manejo por referencia es útil: mejora el uso de memoria o facilita la modificación de un objeto compartido por varias funcionalidades. En otros casos, es un verdadero dolor de cabeza, porque se necesitaría, tal vez, copiar el objeto o duplicarlo. Algunos casos prácticos:
- Imagine que cuenta con una clase Cliente y quiere modificar los datos de un cliente particular (objeto). Empieza a cambiar los valores y luego presiona sobre Cancelar. ¿Cómo vuelve atrás los cambios? Sería genial contar con una copia del original sin modificaciones.
- Suponga ahora que quiere duplicar un objeto de tipo Factura, para no tener que cargar nuevamente todos los datos. ¿Cómo lo hacemos? Recuerde que Factura2 = Factura1 no genera un objeto nuevo y distinto.
Es difícil plantear una clonación genérica porque no se puede violar el principio de ocultamiento de la Programación Orientada a Objetos. Es decir, un clonador genérico solamente puede utilizar las propiedades públicas de un objeto. De ser necesario, cada clase, que tiene acceso a sus atributos ocultos, podría implementar un método para clonarse, de la forma:
DEFINE CLASS UnaClase AS CUSTOM
HIDDEN propiedad1
HIDDEN propiedad2
HIDDEN propiedad3
PROCEDURE Clonar() AS OBJECT
obj2 = CREATEOBJECT("UnaClase")
obj2.setPropiedad1(THIS.propiedad1)
obj2.setPropiedad2(THIS.propiedad2)
obj2.setPropiedad3(THIS.propiedad3)
RETURN obj2
ENDPROC
* Faltarían los PROCEDURES para setPropiedad1, setPropiedad2 y setPropiedad3
ENDDEFINE
Engorroso, ¿no? Imagine un objeto con muchas propiedades ocultas. Sin embargo, a veces, suele ser la única opción.Clase Clonador
Volviendo al caso de los objetos con propiedades públicas, hace un tiempo desarrollé una clase para que funcione como clonador genérico. Obviamente, funciona solamente para las propiedades públicas de los objetos, pero muchas veces suele ser suficiente con esto. La clase se basa en el uso de macrosustitución y las funciones AMEMBERS, PEMSTATUS y EVALUATE. A continuación se ve un PRG con la clase Clonador más una clase llamada UnaClase, para hacer una prueba.
LOCAL obj1, obj2
obj1 = CREATEOBJECT("UnaClase")
clonador = CREATEOBJECT("Clonador")
obj2 = clonador.clonar(obj1)
obj2.propiedad = .NULL.
? obj1.propiedad
? obj2.propiedad
DEFINE CLASS UnaClase AS CUSTOM
propiedad = ""
PROCEDURE INIT()
THIS.propiedad = CREATEOBJECT("Collection")
THIS.propiedad.ADD(CREATEOBJECT("ClaseAuxiliar", "Hola"))
THIS.propiedad.ADD(CREATEOBJECT("ClaseAuxiliar", "Chau"))
ENDPROC
ENDDEFINE
DEFINE CLASS ClaseAuxiliar AS CUSTOM
propiedadAux = ""
PROCEDURE INIT(valor)
THIS.propiedadAux = valor
ENDPROC
ENDDEFINE
DEFINE CLASS Clonador AS CUSTOM
PROCEDURE clonar(OBJREF AS OBJECT) AS OBJECT
IF VARTYPE(OBJREF) <> "O"
RETURN OBJREF
ENDIF
LOCAL ARRAY laMiembros(1,3)
LOCAL i AS INTEGER, objRef2 AS OBJECT, lcPropiedad AS STRING, lcClaseBase AS STRING
objRef2 = CREATEOBJECT(OBJREF.CLASS)
FOR i = 1 TO AMEMBERS(laMiembros, OBJREF, 1, "G")
IF ALLTRIM(UPPER(laMiembros[i,2])) == "PROPERTY"
lcPropiedad = laMiembros[i,1]
lcClaseBase = "objRef." + lcPropiedad + ".BaseClass"
DO CASE
CASE TYPE("objRef." + lcPropiedad) == "O" AND ALLTRIM(UPPER(EVALUATE(lcClaseBase))) == "COLLECTION"
objRef2.&lcPropiedad = THIS.clonarColeccion(EVALUATE("objRef." + lcPropiedad))
CASE TYPE("objRef." + lcPropiedad) == "O"
objRef2.&lcPropiedad = THIS.clonar(EVALUATE("objRef." + lcPropiedad))
CASE TYPE("objRef." + lcPropiedad, 1) == "A"
THIS.clonarArray(OBJREF, lcPropiedad, objRef2, lcPropiedad)
OTHERWISE
IF NOT PEMSTATUS(objRef2, lcPropiedad, 1) && No es de solo lectura.
objRef2.&lcPropiedad = EVALUATE("objRef." + lcPropiedad)
ENDIF
ENDCASE
ENDIF
ENDFOR
RETURN objRef2
ENDPROC
HIDDEN PROCEDURE clonarArray(objOrigen, cOrigen, objDestino, cDestino)
LOCAL ARRAY aOrigen(1), aDestino(1)
ACOPY(objOrigen.&cOrigen, aOrigen)
LOCAL i AS INTEGER, j AS INTEGER
IF ALEN(aOrigen, 2) == 0
DIMENSION objDestino.&cDestino(ALEN(aOrigen, 1))
FOR i = 1 TO ALEN(aOrigen, 1)
objDestino.&cDestino[i] = THIS.clonar(aOrigen[i])
ENDFOR
ELSE
DIMENSION objDestino.&cDestino(ALEN(aOrigen, 1), ALEN(aOrigen, 2))
FOR i = 1 TO ALEN(aOrigen, 1)
FOR j = 1 TO ALEN(aOrigen, 2)
objDestino.&cDestino[i, j] = THIS.clonar(aOrigen[i, j])
ENDFOR
ENDFOR
ENDIF
ENDPROC
HIDDEN PROCEDURE clonarColeccion(objCol AS COLLECTION) AS COLLECTION
LOCAL oClon AS COLLECTION
oClon = CREATEOBJECT("Collection")
LOCAL obj AS OBJECT
FOR EACH obj IN objCol
oClon.ADD(THIS.clonar(obj))
ENDFOR
RETURN oClon
ENDPROC
ENDDEFINE
Note que de esta forma se puede modificar el atributo propiedad de obj2 sin perjudicar al objeto apuntado por la referencia obj1. En este caso, existen dos instancias de la clase UnaClase: la apuntada por obj1, definida explícitamente, y la apuntada por obj2, creada por reflexión de código dentro del método clonar, en la línea objRef2 = CREATEOBJECT(objRef.Class).Note también que los Arrays y los objetos de tipo Collection merecen un tratamiento especial. El ejemplo está armado a propósito con objetos de tipo Collection.
Espero que les sea útil.
Pablo Lissa
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.