Objetos mutables vs inmutables

173

Estoy tratando de entender los objetos mutables vs inmutables. El uso de objetos mutables recibe mucha mala prensa (por ejemplo, devolver una serie de cadenas de un método) pero tengo problemas para comprender cuáles son los impactos negativos de esto. ¿Cuáles son las mejores prácticas en torno al uso de objetos mutables? ¿Deberías evitarlos siempre que sea posible?

Alex Angas
fuente
stringes inmutable, al menos en .NET, y creo que también en muchos otros lenguajes modernos.
Domenic
44
depende de lo que realmente es una cadena en el lenguaje: las 'cadenas' de erlang son solo una serie de entradas, y las "cadenas" de haskell son matrices de caracteres.
Chii
44
Las cadenas de rubíes son una matriz mutable de bytes. Absolutamente me vuelve loco que pueda cambiarlos en su lugar.
Daniel Spiewak
66
Daniel, ¿por qué? ¿Te encuentras haciéndolo por accidente?
55
@DanielSpiewak La solución para que te vuelvas loco y puedas cambiar las cadenas en su lugar es simple: simplemente no lo hagas. La solución para volverse loco porque no puede cambiar las cadenas en su lugar no es tan simple.
Kaz

Respuestas:

162

Bueno, hay algunos aspectos de esto.

  1. Los objetos mutables sin identidad de referencia pueden causar errores en momentos extraños. Por ejemplo, considere un Personbean con un equalsmétodo basado en valores :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    La Personinstancia se "pierde" en el mapa cuando se usa como clave porque su hashCodeigualdad se basa en valores mutables. Esos valores cambiaron fuera del mapa y todo el hashing se volvió obsoleto. A los teóricos les gusta insistir en este punto, pero en la práctica no he encontrado que sea un gran problema.

  2. Otro aspecto es la "razonabilidad" lógica de su código. Este es un término difícil de definir, que abarca todo, desde la legibilidad hasta el flujo. Genéricamente, debería poder ver un fragmento de código y comprender fácilmente lo que hace. Pero más importante que eso, deberías poder convencerte de que hace lo que hace correctamente . Cuando los objetos pueden cambiar independientemente en diferentes "dominios" de código, a veces se hace difícil hacer un seguimiento de qué es dónde y por qué (" acción espeluznante a distancia "). Este es un concepto más difícil de ejemplificar, pero es algo que a menudo se enfrenta en arquitecturas más grandes y complejas.

  3. Finalmente, los objetos mutables son asesinos en situaciones concurrentes. Cada vez que accede a un objeto mutable desde subprocesos separados, debe lidiar con el bloqueo. Esto reduce el rendimiento y hace que su código sea mucho más difícil de mantener. Un sistema suficientemente complicado elimina este problema de forma tan desproporcionada que resulta casi imposible de mantener (incluso para expertos en concurrencia).

Los objetos inmutables (y más particularmente, las colecciones inmutables) evitan todos estos problemas. Una vez que comprenda cómo funcionan, su código se convertirá en algo que es más fácil de leer, más fácil de mantener y menos propenso a fallar de maneras extrañas e impredecibles. Los objetos inmutables son aún más fáciles de probar, debido no solo a su facilidad de imitación, sino también a los patrones de código que tienden a imponer. En resumen, ¡son buenas prácticas por todas partes!

Dicho esto, apenas soy un fanático en este asunto. Algunos problemas simplemente no se modelan bien cuando todo es inmutable. Pero sí creo que debería intentar empujar la mayor parte de su código en esa dirección como sea posible, suponiendo, por supuesto, que está utilizando un lenguaje que hace que esta sea una opinión sostenible (C / C ++ lo hace muy difícil, al igual que Java) . En resumen: las ventajas dependen un poco de su problema, pero preferiría la inmutabilidad.

Daniel Spiewak
fuente
11
Gran respuesta Sin embargo, una pequeña pregunta: ¿C ++ no tiene un buen soporte para la inmutabilidad? ¿No es suficiente la característica de corrección const ?
Dimitri C.
1
@DimitriC .: C ++ en realidad tiene algunas características más fundamentales, sobre todo una mejor distinción entre las ubicaciones de almacenamiento que se supone que encapsulan el estado no compartido de las que encapsulan la identidad del objeto.
supercat
2
En el caso de la programación en Java, Joshua Bloch ofrece una buena explicación sobre este tema en su famoso libro Effective Java (Item 15)
dMathieuD
27

Objetos inmutables versus colecciones inmutables

Uno de los puntos más finos en el debate sobre los objetos mutables frente a los inmutables es la posibilidad de extender el concepto de inmutabilidad a las colecciones. Un objeto inmutable es un objeto que a menudo representa una única estructura lógica de datos (por ejemplo, una cadena inmutable). Cuando tiene una referencia a un objeto inmutable, el contenido del objeto no cambiará.

Una colección inmutable es una colección que nunca cambia.

Cuando realizo una operación en una colección mutable, entonces cambio la colección en su lugar, y todas las entidades que tienen referencias a la colección verán el cambio.

Cuando realizo una operación en una colección inmutable, se devuelve una referencia a una nueva colección que refleja el cambio. Todas las entidades que tienen referencias a versiones anteriores de la colección no verán el cambio.

Las implementaciones inteligentes no necesariamente necesitan copiar (clonar) toda la colección para proporcionar esa inmutabilidad. El ejemplo más simple es la pila implementada como una lista vinculada individualmente y las operaciones push / pop. Puede reutilizar todos los nodos de la colección anterior en la nueva colección, agregando solo un nodo para la inserción y sin clonar nodos para el pop. La operación push_tail en una lista vinculada individualmente, por otro lado, no es tan simple o eficiente.

Variables / referencias inmutables frente a variables mutables

Algunos lenguajes funcionales toman el concepto de inmutabilidad para objetar referencias, permitiendo solo una asignación de referencia única.

  • En Erlang esto es cierto para todas las "variables". Solo puedo asignar objetos a una referencia una vez. Si tuviera que operar en una colección, no podría reasignar la nueva colección a la referencia anterior (nombre de la variable).
  • Scala también incorpora esto al lenguaje con todas las referencias declaradas con var o val , vals solo como asignación única y promoviendo un estilo funcional, pero vars permitiendo una estructura de programa más similar a C o Java.
  • Se requiere la declaración var / val, mientras que muchos lenguajes tradicionales usan modificadores opcionales como final en java y const en C.

Facilidad de desarrollo frente a rendimiento

Casi siempre la razón para usar un objeto inmutable es promover una programación libre de efectos secundarios y un razonamiento simple sobre el código (especialmente en un entorno altamente concurrente / paralelo). No tiene que preocuparse de que otra entidad cambie los datos subyacentes si el objeto es inmutable.

El principal inconveniente es el rendimiento. Aquí hay una reseña de una prueba simple que hice en Java comparando algunos objetos inmutables frente a mutables en un problema de juguete.

Los problemas de rendimiento son discutibles en muchas aplicaciones, pero no en todas, razón por la cual muchos paquetes numéricos grandes, como la clase Numpy Array en Python, permiten actualizaciones in situ de matrices grandes. Esto sería importante para las áreas de aplicación que utilizan operaciones de matriz y vectores grandes. Estos grandes problemas de datos paralelos y computacionalmente intensivos logran una gran aceleración al operar en el lugar.

Ben Jackson
fuente
12

Consulte esta publicación de blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Explica por qué los objetos inmutables son mejores que los mutables. En breve:

  • Los objetos inmutables son más simples de construir, probar y usar
  • los objetos verdaderamente inmutables siempre son seguros para subprocesos
  • ayudan a evitar el acoplamiento temporal
  • su uso es libre de efectos secundarios (sin copias defensivas)
  • se evita el problema de mutabilidad de identidad
  • siempre tienen falla atomicidad
  • son mucho más fáciles de almacenar en caché
yegor256
fuente
10

Los objetos inmutables son un concepto muy poderoso. Quitan gran parte de la carga de tratar de mantener objetos / variables consistentes para todos los clientes.

Puede usarlos para objetos no polimórficos de bajo nivel, como una clase CPoint, que se usan principalmente con semántica de valor.

O puede usarlos para interfaces polimórficas de alto nivel, como una función IF que representa una función matemática, que se usa exclusivamente con semántica de objetos.

Mayor ventaja: la inmutabilidad + semántica de objetos + punteros inteligentes hacen que la propiedad del objeto no sea un problema, todos los clientes del objeto tienen su propia copia privada por defecto. Implícitamente, esto también significa un comportamiento determinista en presencia de concurrencia.

Desventaja: cuando se usa con objetos que contienen muchos datos, el consumo de memoria puede convertirse en un problema. Una solución a esto podría ser mantener las operaciones en un objeto simbólico y hacer una evaluación perezosa. Sin embargo, esto puede conducir a cadenas de cálculos simbólicos, que pueden influir negativamente en el rendimiento si la interfaz no está diseñada para acomodar operaciones simbólicas. Algo que definitivamente debe evitarse en este caso es devolver grandes cantidades de memoria de un método. En combinación con operaciones simbólicas encadenadas, esto podría conducir a un consumo masivo de memoria y una degradación del rendimiento.

Así que los objetos inmutables son definitivamente mi forma principal de pensar sobre el diseño orientado a objetos, pero no son un dogma. Resuelven muchos problemas para clientes de objetos, pero también crean muchos, especialmente para los implementadores.

QBziZ
fuente
Creo que entendí mal la Sección 4 para obtener la mayor ventaja: inmutabilidad + semántica de objetos + punteros inteligentes hace que la propiedad de objetos sea un punto "discutible". ¿Entonces es discutible? Creo que está usando "discutible" incorrectamente ... Al ver que la siguiente oración es el objeto ha implicado "comportamiento determinista" de su comportamiento "discutible" (discutible).
Benjamin
Tienes razón, usé 'discutible' incorrectamente. Considere que cambió :)
QBziZ
6

Debe especificar de qué idioma está hablando. Para lenguajes de bajo nivel como C o C ++, prefiero usar objetos mutables para ahorrar espacio y reducir la pérdida de memoria. En los lenguajes de nivel superior, los objetos inmutables hacen que sea más fácil razonar sobre el comportamiento del código (especialmente el código multiproceso) porque no hay una "acción espeluznante a distancia".

John Millikin
fuente
¿Estás sugiriendo que los hilos están enredados cuánticamente? Eso es bastante exagerado :) Los hilos son en realidad, si lo piensas, bastante cerca de enredarse. Un cambio que hace un hilo afecta al otro. +1
ndrewxie
4

Un objeto mutable es simplemente un objeto que se puede modificar después de su creación / instancia, frente a un objeto inmutable que no se puede modificar (consulte la página de Wikipedia sobre el tema). Un ejemplo de esto en un lenguaje de programación son las listas y tuplas de Python. Las listas se pueden modificar (p. Ej., Se pueden agregar nuevos elementos después de crearlos) mientras que las tuplas no.

Realmente no creo que haya una respuesta clara sobre cuál es mejor para todas las situaciones. Ambos tienen sus lugares.

willurd
fuente
1

Si un tipo de clase es mutable, una variable de ese tipo de clase puede tener varios significados diferentes. Por ejemplo, suponga que un objeto footiene un campo int[] arry contiene una referencia a una int[3]retención de los números {5, 7, 9}. Aunque se conoce el tipo de campo, hay al menos cuatro cosas diferentes que puede representar:

  • Una referencia potencialmente compartida, cuyos titulares solo se preocupan de que encapsule los valores 5, 7 y 9. Si foodesea arrencapsular valores diferentes, debe reemplazarlo con una matriz diferente que contenga los valores deseados. Si se quiere hacer una copia foo, se puede dar a la copia una referencia arro una nueva matriz que contenga los valores {1,2,3}, lo que sea más conveniente.

  • La única referencia, en cualquier parte del universo, a una matriz que encapsula los valores 5, 7 y 9. conjunto de tres ubicaciones de almacenamiento que actualmente contienen los valores 5, 7 y 9; si fooquiere que encapsule los valores 5, 8 y 9, puede cambiar el segundo elemento de esa matriz o crear una nueva matriz que contenga los valores 5, 8 y 9 y abandonar el anterior. Tenga en cuenta que si se desea hacer una copia foo, se debe reemplazar en la copia arrcon una referencia a una nueva matriz para foo.arrpoder permanecer como la única referencia a esa matriz en cualquier parte del universo.

  • Una referencia a una matriz que es propiedad de otro objeto al que la ha expuesto foopor alguna razón (por ejemplo, tal vez quiera fooalmacenar algunos datos allí). En este escenario, arrno encapsula el contenido de la matriz, sino su identidad . Debido a que reemplazar arrcon una referencia a una nueva matriz cambiaría totalmente su significado, una copia de foodebería contener una referencia a la misma matriz.

  • Una referencia a una matriz de la cual fooes el único propietario, pero a la cual las referencias son retenidas por otro objeto por alguna razón (por ejemplo, quiere tener el otro objeto para almacenar datos allí, el otro lado del caso anterior). En este escenario, arrencapsula tanto la identidad de la matriz como sus contenidos. Reemplazar arrcon una referencia a una nueva matriz cambiaría totalmente su significado, pero tener una arrreferencia de clon foo.arrviolaría la suposición de que fooes el único propietario. Por lo tanto, no hay forma de copiar foo.

En teoría, int[]debería ser un buen tipo simple y bien definido, pero tiene cuatro significados muy diferentes. Por el contrario, una referencia a un objeto inmutable (por ejemplo String) generalmente solo tiene un significado. Gran parte del "poder" de los objetos inmutables se deriva de ese hecho.

Super gato
fuente
1

Las instancias mutables se pasan por referencia.

Las instancias inmutables se pasan por valor.

Ejemplo abstracto. Supongamos que existe un archivo llamado txtfile en mi HDD. Ahora, cuando me pidas txtfile , puedo devolverlo en dos modos:

  1. Cree un acceso directo a txtfile y pas acceso directo a usted, o
  2. Tome una copia para txtfile y pas copy para usted.

En el primer modo, devuelto txtfile es un archivo mutable, porque cuando haces cambios en el archivo de acceso directo, también haces cambios en el archivo original. La ventaja de este modo es que cada acceso directo devuelto requiere menos memoria (en RAM o en HDD) y la desventaja es que todos (no solo yo, el propietario) tenemos permisos para modificar el contenido del archivo.

En el segundo modo, txtfile devuelto es un archivo inmutable, porque todos los cambios en el archivo recibido no se refieren al archivo original. La ventaja de este modo es que solo yo (el propietario) puede modificar el archivo original y la desventaja es que cada copia devuelta requiere memoria (en RAM o en HDD).

Teodor
fuente
Esto no es estrictamente cierto. Las instancias inmutables se pueden pasar por referencia, y con frecuencia lo son. La copia se realiza principalmente cuando necesita actualizar el objeto o la estructura de datos.
Jamon Holmgren
0

Si devuelve referencias de una matriz o cadena, el mundo exterior puede modificar el contenido de ese objeto y, por lo tanto, hacerlo como objeto mutable (modificable).

usuario1923551
fuente
0

Inmutable significa que no se puede cambiar, y mutable significa que puede cambiar.

Los objetos son diferentes a los primitivos en Java. Las primitivas se construyen en tipos (booleanos, int, etc.) y los objetos (clases) son tipos creados por el usuario.

Las primitivas y los objetos pueden ser mutables o inmutables cuando se definen como variables miembro dentro de la implementación de una clase.

Mucha gente piensa que las primitivas y las variables de objeto que tienen un modificador final frente a ellas son inmutables, sin embargo, esto no es exactamente cierto. Así que final casi no significa inmutable para las variables. Vea el ejemplo aquí
http://www.siteconsortium.com/h/D0000F.php .

JTHouseCat
fuente
0

Immutable object- es un estado de objeto del cual no se puede cambiar después de la creación. El objeto es inmutable cuando todos sus campos son inmutables

A salvo de amenazas

La principal ventaja del objeto inmutable es que es un entorno natural para concurrente. El mayor problema en la concurrencia es shared resourceque se puede cambiar cualquiera de los hilos. Pero si un objeto es inmutable, es read-onlyla operación segura para subprocesos. Cualquier modificación de un objeto inmutable original devuelve una copia

sin efectos secundarios

Como desarrollador, está completamente seguro de que el estado del objeto inmutable no se puede cambiar desde ningún lugar (a propósito o no)

optimización de compilación

Mejorar el rendimiento

Desventaja:

Copiar un objeto es una operación más pesada que cambiar un objeto mutable, es por eso que tiene una huella de rendimiento

Para crear un immutableobjeto debes usar:

  1. Nivel de idioma. Cada idioma contiene herramientas para ayudarlo. Por ejemplo, Java tiene finaly primitives, Swift tiene lety struct[Acerca de] . El lenguaje define un tipo de variable. Por ejemplo, Java tiene primitivey referenceescribe, Swift tiene valuey referenceescribe [Acerca de] . Para los objetos inmutables es más conveniente escribir primitivesy valueescribir una copia de forma predeterminada. En cuanto al referencetipo, es más difícil (porque puedes cambiar el estado del objeto) pero es posible. Por ejemplo, puede usar el clonepatrón en un nivel de desarrollador

  2. Nivel de desarrollador. Como desarrollador, no debe proporcionar una interfaz para cambiar de estado

yoAlex5
fuente