Copia de objetos

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
Técnica en programación orientada al objeto

En la programación orientada a objetos, copiar objetos es crear una copia de un objeto existente, una unidad de datos en la programación orientada a objetos. El objeto resultante se denomina copia de objeto o simplemente copia del objeto original. La copia es básica pero tiene sutilezas y puede tener una sobrecarga significativa. Hay varias formas de copiar un objeto, más comúnmente mediante un constructor de copia o clonación. La copia se realiza principalmente para que la copia se pueda modificar o mover, o para conservar el valor actual. Si alguno de estos no es necesario, una referencia a los datos originales es suficiente y más eficiente, ya que no se produce ninguna copia.

Los objetos en general almacenan datos compuestos. Mientras que en casos sencillos la copia se puede realizar asignando un objeto nuevo no inicializado y copiando todos los campos (atributos) del objeto original, en casos más complejos esto no da como resultado el comportamiento deseado.

Métodos de copia

El objetivo de diseño de la mayoría de los objetos es dar la apariencia de estar hechos de un bloque monolítico, aunque la mayoría no lo esté. Como los objetos se componen de varias partes diferentes, la copia se convierte en algo no trivial. Existen varias estrategias para tratar este problema.

Considere un objeto A, que contiene campos xi (más concretamente, considere si A es una cadena y xi es una matriz de sus caracteres). Existen diferentes estrategias para hacer una copia de A, denominadas copia superficial y copia profunda. Muchos lenguajes permiten la copia genérica por una o cualquiera de las estrategias, definiendo una operación de copia o operaciones separadas de copia superficial y copia profunda. Tenga en cuenta que aún más superficial es usar una referencia al objeto A existente, en cuyo caso no hay un objeto nuevo, solo una referencia nueva.

La terminología de copia superficial y copia profunda data de Smalltalk-80. La misma distinción es válida para comparar objetos por igualdad: más básicamente, hay una diferencia entre identidad (mismo objeto) e igualdad (mismo valor), que corresponde a igualdad superficial y (1 nivel) igualdad profunda de dos referencias de objeto, pero además si la igualdad significa comparar solo los campos del objeto en cuestión o desreferenciar algunos o todos los campos y comparar sus valores a su vez (por ejemplo, ¿dos listas vinculadas son iguales si tienen los mismos nodos o si tienen los mismos valores?).

Copia superficial

Un método para copiar un objeto es la copia superficial. En ese caso, se crea un nuevo objeto B y los valores de los campos de A se copian en B. Esto también se conoce como copia campo por campo, campo por campo copia, o copia de campo. Si el valor del campo es una referencia a un objeto (por ejemplo, una dirección de memoria), copia la referencia, por lo tanto, se refiere al mismo objeto que hace A, y si el valor del campo es un tipo primitivo, copia el valor del tipo primitivo. En lenguajes sin tipos primitivos (donde todo es un objeto), todos los campos de la copia B son referencias a los mismos objetos que los campos del original A. Los objetos referenciados son por lo tanto compartidos, así que si uno de estos objetos se modifica (desde A o B), el cambio es visible en el otro. Las copias superficiales son simples y, por lo general, baratas, ya que generalmente se pueden implementar simplemente copiando los bits exactamente.

Copia profunda

A deep copy in progress.
Una copia profunda en progreso.
A deep copy having been completed.
Se ha completado una copia profunda.

Una alternativa es una copia profunda, lo que significa que se eliminan las referencias a los campos: en lugar de referencias a los objetos que se copian, se crean nuevos objetos de copia para cualquier objeto referenciado, y las referencias a estos se colocan en B.

Combinación

En casos más complejos, algunos campos en una copia deberían tener valores compartidos con el objeto original (como en una copia superficial), correspondientes a una "asociación" relación; y algunos campos deben tener copias (como en una copia profunda), correspondientes a una "agregación" relación. En estos casos, generalmente se requiere una implementación personalizada de la copia; este problema y la solución datan de Smalltalk-80. Alternativamente, los campos se pueden marcar para que requieran una copia superficial o una copia profunda, y las operaciones de copia se generan automáticamente (al igual que para las operaciones de comparación). Sin embargo, esto no está implementado en la mayoría de los lenguajes orientados a objetos, aunque hay soporte parcial en Eiffel.

Implementación

Casi todos los lenguajes de programación orientados a objetos proporcionan alguna forma de copiar objetos. Como la mayoría de los lenguajes no proporcionan la mayoría de los objetos para los programas, un programador debe definir cómo debe copiarse un objeto, al igual que debe definir si dos objetos son idénticos o incluso comparables en primer lugar. Muchos idiomas proporcionan algún comportamiento predeterminado.

Cómo se resuelve la copia varía de un idioma a otro, y qué concepto de objeto tiene.

Copia perezosa

Una copia diferida es una implementación de una copia profunda. Al copiar inicialmente un objeto, se utiliza una copia superficial (rápida). También se utiliza un contador para rastrear cuántos objetos comparten los datos. Cuando el programa quiere modificar un objeto, puede determinar si los datos se comparten (al examinar el contador) y puede hacer una copia profunda si es necesario.

Lazy copy se ve en el exterior como una copia profunda, pero aprovecha la velocidad de una copia superficial siempre que sea posible. La desventaja son los costos básicos bastante altos pero constantes debido al mostrador. Además, en ciertas situaciones, las referencias circulares pueden causar problemas.

Lazy copy está relacionada con copy-on-write.

En Java

A continuación se presentan ejemplos de uno de los lenguajes orientados a objetos más utilizados, Java, que debería cubrir casi todas las formas en que un lenguaje orientado a objetos puede tratar este problema.

A diferencia de C++, en Java siempre se accede indirectamente a los objetos a través de referencias. Los objetos nunca se crean implícitamente, sino que siempre se pasan o asignan mediante una variable de referencia. (Los métodos en Java son siempre pasar por valor, sin embargo, es el valor de la variable de referencia lo que se pasa). ya alcanzable. No hay una forma automática de copiar ningún objeto dado en Java.

La copia generalmente se realiza mediante un método clone() de una clase. Por lo general, este método, a su vez, llama al método clone() de su clase principal para obtener una copia y luego realiza los procedimientos de copia personalizados. Eventualmente, esto llega al método clone() de Object (la clase superior), que crea una nueva instancia de la misma clase que el objeto y copia todos los campos a la nueva instancia (un "copia superficial"). Si se usa este método, la clase debe implementar la interfaz de marcador Cloneable, o de lo contrario generará una CloneNotSupportedException. Después de obtener una copia de la clase principal, una clase' propio método clone() puede proporcionar una capacidad de clonación personalizada, como copia profunda (es decir, duplicar algunas de las estructuras a las que hace referencia el objeto) o dar a la nueva instancia una nueva ID única.

El tipo de devolución de clone() es Object, pero los implementadores de un método de clonación podrían escribir el tipo del objeto que se clona debido al soporte de Java para tipos de devolución covariantes. Una ventaja de usar clone() es que, dado que es un método reemplazable, podemos llamar a clone() en cualquier objeto, y utilizará el método clone() de su clase, sin que el código de llamada necesite saber cuál es esa clase. (que sería necesario con un constructor de copias).

Una desventaja es que a menudo no se puede acceder al método clone() en un tipo abstracto. La mayoría de las interfaces y clases abstractas en Java no especifican un método clone() público. Por lo tanto, a menudo la única forma de usar el método clone() es si se conoce la clase de un objeto, lo cual es contrario al principio de abstracción de usar el tipo más genérico posible. Por ejemplo, si uno tiene una referencia de List en Java, no puede invocar clone() en esa referencia porque List no especifica ningún método clone() público. Las implementaciones de List como ArrayList y LinkedList generalmente tienen métodos clone(), pero es inconveniente y una mala abstracción llevar el tipo de clase de un objeto.

Otra forma de copiar objetos en Java es serializarlos a través de la interfaz Serializable. Por lo general, esto se usa con fines de persistencia y protocolo de conexión, pero crea copias de objetos y, a diferencia de la clonación, una copia profunda que maneja correctamente los gráficos cíclicos de objetos está fácilmente disponible con un esfuerzo mínimo por parte de un programador.

Ambos métodos sufren de un problema notable: el constructor no se usa para objetos copiados con clonación o serialización. Esto puede generar errores con datos inicializados incorrectamente, impide el uso de campos de miembros finales y dificulta el mantenimiento. Algunas utilidades intentan superar estos problemas mediante el uso de la reflexión para objetos de copia profunda, como la biblioteca de clonación profunda.

En Eiffel

Los objetos de tiempo de ejecución en Eiffel son accesibles indirectamente a través de referencias o como objetos expandidos cuyos campos están incrustados dentro de los objetos que los usan. Es decir, los campos de un objeto se almacenan externa o internamente.

La clase Eiffel ANY contiene funciones para la copia y clonación superficial y profunda de objetos. Todas las clases de Eiffel heredan de ANY, por lo que estas funciones están disponibles en todas las clases y se aplican tanto a objetos de referencia como expandidos.

La función copiar realiza una copia superficial, campo por campo, de un objeto a otro. En este caso no se crea ningún objeto nuevo. Si y se copiaron en x, entonces los mismos objetos a los que hace referencia y antes de la aplicación de copiar, también será referenciado por x después de copiar función completa.

Para efectuar la creación de un nuevo objeto que es un duplicado superficial de y, se usa la característica gemelo. En este caso, se crea un nuevo objeto con sus campos idénticos a los del origen.

La función gemelo se basa en la función copiar, que se puede redefinir en los descendientes de CUALQUIER, si es necesario. El resultado de gemelo es del tipo anclado como Actual.

La copia profunda y la creación de gemelos profundos se pueden hacer usando las funciones deep_copy y deep_twin, nuevamente heredadas de la clase CUALQUIERA. Estas funciones tienen el potencial de crear muchos objetos nuevos, porque duplican todos los objetos en una estructura de objeto completa. Debido a que se crean nuevos objetos duplicados en lugar de simplemente copiar referencias a objetos existentes, las operaciones profundas se convertirán en una fuente de problemas de rendimiento más fácilmente que las operaciones superficiales.

En otros idiomas

En C#, en lugar de usar la interfaz ICloneable, se puede usar un método de extensión genérico para crear una copia profunda usando la reflexión. Esto tiene dos ventajas: en primer lugar, brinda la flexibilidad de copiar todos los objetos sin tener que especificar cada propiedad y variable para que se copien manualmente. En segundo lugar, debido a que el tipo es genérico, el compilador se asegura de que el objeto de destino y el objeto de origen tengan el mismo tipo.

En Objective-C, los métodos copy y mutableCopy son heredados por todos los objetos y destinados a realizar copias; el último es para crear un tipo mutable del objeto original. Estos métodos, a su vez, llaman a los métodos copyWithZone y mutableCopyWithZone, respectivamente, para realizar la copia. Un objeto debe implementar el método copyWithZone correspondiente para ser copiable.

En OCaml, la función de biblioteca Oo.copy realiza una copia superficial de un objeto.

En Python, el módulo de copia de la biblioteca proporciona copia superficial y copia profunda de objetos a través de las funciones copy() y deepcopy(), respectivamente. Los programadores pueden definir métodos especiales __copy__() y __deepcopy__() en un objeto para proporcionar una implementación de copia personalizada.

En Ruby, todos los objetos heredan dos métodos para realizar copias superficiales, clonar y duplicar. Los dos métodos se diferencian en que clone copia el estado corrupto, el estado congelado y cualquier método singleton que pueda tener un objeto, mientras que dup copia solo su estado corrupto. Las copias profundas se pueden lograr volcando y cargando el flujo de bytes de un objeto o la serialización YAML.[1] Alternativamente, puede usar la gema deep_dive para hacer una copia profunda controlada de sus gráficos de objetos. [2]

En Perl, las estructuras anidadas se almacenan mediante el uso de referencias, por lo que un desarrollador puede recorrer toda la estructura y volver a hacer referencia a los datos o usar dclone() función del módulo Almacenable.

En VBA, una asignación de variables de tipo Object es una copia superficial, una asignación para todos los demás tipos (tipos numéricos, cadenas, tipos definidos por el usuario, matrices) es una copia profunda. Entonces, la palabra clave Set para una tarea señala una copia superficial y la palabra clave (opcional) Let señala una copia profunda. No hay un método integrado para copias profundas de Objetos en VBA.

Contenido relacionado

Una carta abierta a los aficionados

"Carta abierta a los aficionados" es una carta abierta de 1976 escrita por Bill Gates, el cofundador de Microsoft, a los primeros aficionados a las...

Phoebe (computadora)

El Phoebe 2100 iba a ser Acorn Computers' sucesor del RiscPC, cuyo lanzamiento estaba previsto para finales de 1998. Sin embargo, en septiembre de 1998...

Portabilidad

En ingeniería de software, portar es el proceso de adaptar el software con el fin de lograr alguna forma de ejecución en un entorno informático que sea...
Más resultados...
Tamaño del texto: