En la programación orientada a objetos , la copia de objetos consiste en 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 copias o clonación . La copia se realiza principalmente para que la copia se pueda modificar o mover, o se conserve el valor actual. Si alguno de estos es innecesario, una referencia a los datos originales es suficiente y más eficiente, ya que no se realiza ninguna copia.
Los objetos en general almacenan datos compuestos . Mientras que en casos simples se puede copiar 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 del 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 no es trivial. Existen varias estrategias para tratar este problema.
Considere un objeto A, que contiene campos x i (más concretamente, considere si A es una cadena y x i 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 mediante una o cualquiera de las estrategias, definiendo una operación de copia o operaciones de copia superficial y profunda separadas . [1] Tenga en cuenta que incluso menos profundo es usar una referencia al objeto A existente, en cuyo caso no hay ningún objeto nuevo, solo una nueva referencia.
La terminología de copia superficial y copia profunda se remonta a Smalltalk -80. [2] La misma distinción es válida para comparar objetos por igualdad: básicamente hay una diferencia entre identidad (mismo objeto) e igualdad (mismo valor), correspondiente a igualdad superficial y (1 nivel) igualdad profunda de dos referencias de objeto, pero luego 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, ¿son dos listas enlazadas iguales si tienen los mismos nodos o si tienen los mismos valores?). [ aclaración necesaria ]
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. [3] [4] [5] Esto también se conoce como copia campo por campo , [6] [7] [8] copia de campo por campo o copia de campo . [9] 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 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 se comparten , por lo tanto, si uno de estos objetos se modifica (de 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
Una alternativa es una copia profunda, lo que significa que los campos están desreferenciados: en lugar de referencias a los objetos que se están copiando, se crean nuevos objetos de copia para cualquier objeto referenciado y las referencias a estos se colocan en B. El resultado es diferente del resultado que da una copia superficial. en que los objetos referenciados por la copia B son distintos de los referenciados por A, e independientes. Las copias profundas son más caras, debido a la necesidad de crear objetos adicionales, y pueden ser sustancialmente más complicadas, debido a que las referencias posiblemente forman un gráfico complicado.
La copia profunda es un proceso en el que el proceso de copia se produce de forma recursiva. Significa construir primero un nuevo objeto de colección y luego poblarlo de forma recursiva con copias de los objetos secundarios que se encuentran en el original. En caso de copia profunda, se copia una copia del objeto en otro objeto. Significa que cualquier cambio realizado en una copia del objeto no se refleja en el objeto original. En Python, esto se implementa mediante la función "copia profunda ()".
Combinación
En casos más complejos, algunos campos de una copia deberían tener valores compartidos con el objeto original (como en una copia superficial), correspondientes a una relación de "asociación"; y algunos campos deben tener copias (como en una copia profunda), correspondientes a una relación de "agregación". En estos casos, generalmente se requiere una implementación personalizada de la copia; este problema y su solución se remontan a Smalltalk-80. [10] 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 (también para las operaciones de comparación). [1] Sin embargo, esto no está implementado en la mayoría de los lenguajes orientados a objetos, aunque hay soporte parcial en Eiffel. [1]
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 se debe copiar un objeto, al igual que debe definir si dos objetos son idénticos o incluso comparables en primer lugar. Muchos idiomas ofrecen un comportamiento predeterminado.
La forma en que 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 usa un contador para rastrear cuántos objetos comparten los datos. Cuando el programa quiere modificar un objeto, puede determinar si los datos se comparten (examinando el contador) y puede hacer una copia profunda si es necesario.
La copia diferida 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 base bastante altos pero constantes debido al contador. Además, en determinadas situaciones, las referencias circulares pueden causar problemas.
La copia diferida está relacionada con la copia en escritura .
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 ++, a los objetos en Java siempre se accede indirectamente a través de referencias . Los objetos nunca se crean implícitamente, sino que siempre se pasan o se asignan mediante una variable de referencia. (Los métodos en Java siempre se pasan por valor , sin embargo, es el valor de la variable de referencia que se pasa.) [11] La máquina virtual Java administra la recolección de basura para que los objetos se limpien después de que ya no sean accesibles. No existe una forma automática de copiar un objeto determinado en Java.
La copia generalmente se realiza mediante un método clone () de una clase. Este método generalmente, a su vez, llama al método clone () de su clase principal para obtener una copia y luego realiza cualquier procedimiento de copia personalizado. Finalmente, 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 (una "copia superficial"). Si se usa este método, la clase debe implementar la Cloneable
interfaz del marcador, o de lo contrario lanzará una CloneNotSupportedException. Después de obtener una copia de la clase principal, el método clone () propio de la clase puede proporcionar una capacidad de clonación personalizada, como una 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 retorno de clone () es Object
, pero los implementadores de un método de clonación podrían escribir el tipo de objeto que se está clonando debido al soporte de Java para tipos de retorno covariantes . Una ventaja de usar clone () es que, dado que es un método reemplazable , podemos llamar a clone () en cualquier objeto, y usará 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 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 Serializable
interfaz. Esto se usa típicamente para propósitos de protocolo de persistencia y cableado , pero crea copias de objetos y, a diferencia de la clonación, una copia profunda que maneja gráficamente gráficos de objetos en ciclos está disponible fácilmente con un mínimo esfuerzo por parte de un programador.
Ambos métodos adolecen de un problema notable: el constructor no se utiliza para objetos copiados con clonación o serialización. Esto puede provocar errores con datos inicializados incorrectamente, impide el uso de final
campos miembros y dificulta el mantenimiento. Algunas utilidades intentan superar estos problemas utilizando la reflexión para copiar objetos en profundidad, como la biblioteca de clonación profunda. [12]
En eiffel
Los objetos en 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 ANY
, por lo que estas características están disponibles en todas las clases y son aplicables tanto a objetos de referencia como expandidos.
La copy
función 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 copió en x
, los mismos objetos a los que se hizo referencia y
antes de la aplicación de copy
, también serán referenciados por x
después de que se copy
complete la función.
Para efectuar la creación de un nuevo objeto que es un duplicado superficial de y
, twin
se utiliza la función . En este caso, se crea un nuevo objeto con sus campos idénticos a los de la fuente.
La función se twin
basa en la función copy
, que se puede redefinir en descendientes de ANY
, si es necesario. El resultado de twin
es del tipo anclado like Current
.
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 ANY
. Estas características 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 mediante la reflexión. Esto tiene dos ventajas: en primer lugar, proporciona la flexibilidad de copiar todos los objetos sin tener que especificar cada propiedad y variable que se copiarán 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 están 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 copyWithZone
método correspondiente para poder copiarlo.
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 una copia superficial y una copia profunda de los objetos a través de las funciones copy()
y deepcopy()
, respectivamente. [13] 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
las copias del estado contaminada de un objeto, el estado congelado, y cualquier singleton métodos que pueda tener, mientras que dup
las copias sólo su estado contaminada. Las copias profundas se pueden lograr volcando y cargando el flujo de bytes de un objeto o la serialización YAML. [1] Alternativamente, puedes usar la gema deep_dive para hacer una copia profunda controlada de tus 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 la dclone()
función del módulo Storable .
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, String, tipos definidos por el usuario, matrices) es una copia profunda. Entonces, la palabra clave Set
para una tarea indica una copia superficial y la palabra clave (opcional) Let
indica una copia profunda. No hay un método integrado para copias profundas de objetos en VBA.
Ver también
- Copiar constructor
- Sobrecarga del operador
- Recuento de referencias
- Copiar en escrito
- Clonar (método Java)
Notas
- ^ a b c Grogono y Sakkinen 2000 .
- ^ Goldberg y Robson 1983 , págs. 97–99. "Hay dos formas de hacer copias de un objeto. La distinción es si se copian o no los valores de las variables del objeto. Si los valores no se copian, entonces se comparten (
shallowCopy
); si se copian los valores, entonces se no compartido (deepCopy
) ". - ^ "Explicación de copia superficial vs profunda de C ++" .
- ^ "Explicación de .NET Shallow vs Deep Copy" .
- ^ "Explicación genérica de copia superficial vs profunda" . Archivado desde el original el 4 de marzo de 2016 . Consultado el 10 de abril de 2013 .
- ^ Core Java: Fundamentos, Volumen 1, p. 295
- ^ Eficaz de Java , segunda edición, p. 54
- ^ " ¿Qué es esta copia campo por campo realizada por Object.clone ()? ", Stack Overflow
- ^ "Josh Bloch sobre el diseño: una conversación con el autor eficaz de Java, Josh Bloch", por Bill Venners, JavaWorld , 4 de enero de 2002, p. 13
- ^ Goldberg y Robson 1983 , p. 97. "La implementación predeterminada de
copy
esshallowCopy
. En las subclases en las que la copia debe dar como resultado una combinación especial de variables compartidas y no compartidas, el método asociado con la copia generalmente se vuelve a implementar, en lugar del método asociado conshallowCopy
odeepCopy
". - ^ "Pasar información a un método o un constructor" . Consultado el 8 de octubre de 2013 .
- ^ Biblioteca de clonación profunda de Java
- ^ Módulo de copia de Python
Referencias
- Goldberg, Adele ; Robson, David (1983). Smalltalk-80: El lenguaje y su implementación . Palo Alto, California: Xerox Palo Alto Research Center. ISBN 978-0-201-11371-6.
- Grogono, Peter; Sakkinen, Markku (12 de mayo de 2000). "Copiar y comparar: problemas y soluciones" (PDF) . En Elisa Bertino (ed.). Apuntes de conferencias en Ciencias de la Computación . ECOOP 2000 - Programación orientada a objetos. 1850 . Springer Berlín Heidelberg. págs. 226–250. doi : 10.1007 / 3-540-45102-1_11 . Consultado el 23 de junio de 2015 .