Pregunta

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

¿Fue útil?

Solución

Bueno, hay un par de aspectos para esto. Número uno, los objetos mutables sin identidad de referencia pueden causar errores en momentos impares. Por ejemplo, considere un bean Person con un método basado en valor es igual a :

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

La instancia de Person se pierde " " " en el mapa cuando se usa como una clave porque es hashCode y la igualdad se basa en valores mutables. Esos valores cambiaron fuera del mapa y todo el hash se volvió obsoleto. A los teóricos les gusta insistir en este punto, pero en la práctica no he encontrado que sea un problema.

Otro aspecto es la lógica " razonabilidad " de su código. Este es un término difícil de definir, que abarca desde la legibilidad hasta la fluidez. En general, debería poder mirar un fragmento de código y entender fácilmente lo que hace. Pero más importante que eso, debes poder convencerte de que hace lo que hace correctamente . Cuando los objetos pueden cambiar de forma independiente a través de diferentes dominios de código, a veces resulta difícil hacer un seguimiento de qué es dónde y por qué (acción espeluznante a una 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.

Finalmente, los objetos mutables son killer en situaciones concurrentes. Siempre que acceda a un objeto mutable desde hilos separados, tendrá que lidiar con el bloqueo. Esto reduce el rendimiento y hace que su código dramáticamente sea más difícil de mantener. Un sistema suficientemente complicado soporta este problema tan fuera de proporción que resulta casi imposible de mantener (incluso para los expertos en concurrencia).

Los objetos inmutables (y, más particularmente, las colecciones inmutables) evitan todos estos problemas. Una vez que tenga una idea de 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 es menos probable que falle de formas extrañas e impredecibles. Los objetos inmutables son incluso más fáciles de probar, debido no solo a su facilidad de simulación, sino también a los patrones de código que tienden a aplicar. En resumen, ¡son buenas prácticas en todos los ámbitos!

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

Otros consejos

Objetos inmutables frente a colecciones inmutables

Uno de los puntos más delicados en el debate sobre objetos mutables frente a objetos 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, luego 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 tienen que copiar (clonar) toda la colección para proporcionar esa inmutabilidad. El ejemplo más simple es la pila implementada como una lista enlazada individualmente y las operaciones push / pop. Puede reutilizar todos los nodos de la colección anterior en la nueva colección, agregando solo un solo nodo para la inserción, y clonando no nodos para la ventana emergente. La operación push_tail en una lista enlazada individualmente, por otro lado, no es tan simple o eficiente.

Variables / referencias inmutables vs. mutables

Algunos lenguajes funcionales toman el concepto de inmutabilidad a las referencias de objetos en sí, permitiendo solo una única asignación de referencia.

  • 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 variable).
  • Scala también integra esto en el lenguaje con todas las referencias que se declaran con var o val , vals solo asignación simple y promoción de un estilo funcional, pero vars que permiten un mayor Estructura de programa tipo c o similar a java.
  • Se requiere la declaración var / val, mientras que muchos idiomas tradicionales usan modificadores opcionales como final en java y const en c.

Facilidad de desarrollo vs. rendimiento

Casi siempre la razón para usar un objeto inmutable es promover una programación sin efectos secundarios y un razonamiento simple sobre el código (especialmente en un entorno altamente concurrente / paralelo). No tiene que preocuparse por los datos subyacentes que son cambiados por otra entidad 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 y mutables en un problema de juguete .

Los problemas de rendimiento son discutibles en muchas aplicaciones, pero no en todas, por lo que 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 hacen uso de matrices grandes y operaciones vectoriales. Estos grandes problemas de computación paralela y de uso intensivo de computación logran una gran aceleración al operar en su lugar.

Consulte esta publicación del 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 resumen:

  • los objetos inmutables son más fáciles 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é

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

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

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

La mayor ventaja: 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 de forma predeterminada. Implícitamente, esto también significa un comportamiento determinista en presencia de concurrencia.

Desventaja: cuando se usa con objetos que contienen gran cantidad de datos, el consumo de memoria puede convertirse en un problema. Una solución para esto podría ser mantener las operaciones en un objeto simbólico y realizar una evaluación perezosa. Sin embargo, esto puede llevar 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 se debe evitar en este caso es devolver enormes trozos de memoria de un método. En combinación con operaciones simbólicas encadenadas, esto podría provocar un consumo masivo de memoria y una degradación del rendimiento.

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

Debes especificar de qué idioma estás 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 de múltiples subprocesos) porque no hay "acción espeluznante a una distancia".

Un objeto mutable es simplemente un objeto que se puede modificar después de crearlo / crear una 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 de Pythons y las tuplas. Las listas se pueden modificar (por ejemplo, los nuevos elementos se pueden agregar después de crearlos), mientras que las tuplas no se pueden.

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

Si un tipo de clase es mutable, una variable de ese tipo de clase puede tener varios significados diferentes. Por ejemplo, supongamos que un objeto foo tiene un campo int [] arr , y contiene una referencia a un int [3] que contiene 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, a todos los titulares les importa que encapsule los valores 5, 7 y 9. Si foo quiere que arr encapsule diferentes valores, debe reemplazarlo con una matriz diferente que contiene los valores deseados. Si uno quiere hacer una copia de foo , puede darle a la copia una referencia a arr o una nueva matriz que contenga los valores {1,2,3}, cualquiera que sea es 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 en este momento tienen los valores 5, 7 y 9; Si foo quiere que encapsule los valores 5, 8 y 9, puede cambiar el segundo elemento en esa matriz o crear una nueva matriz que contenga los valores 5, 8 y 9 y abandonar la antigua uno. Tenga en cuenta que si uno desea hacer una copia de foo , en la copia debe reemplazar arr con una referencia a una nueva matriz para foo.arr para permanecer como la única referencia a esa matriz en cualquier parte del universo.

  • Una referencia a una matriz que es propiedad de algún otro objeto que la ha expuesto a foo por alguna razón (por ejemplo, tal vez quiera foo para almacenar algunos datos allí). En este escenario, arr no encapsula el contenido de la matriz, sino su identidad . Debido a que reemplazar arr con una referencia a una nueva matriz cambiaría totalmente su significado, una copia de foo debería contener una referencia a la misma matriz.

  • Una referencia a una matriz de la que foo es el único propietario, pero a la que otro objeto mantiene las referencias por algún motivo (por ejemplo, quiere que el otro objeto almacene datos). allí - la otra cara del caso anterior). En este escenario, arr encapsula tanto la identidad de la matriz como su contenido. Reemplazar arr con una referencia a una nueva matriz cambiaría totalmente su significado, pero tener un arr de un clon se refiere a foo.arr violaría la suposición ese foo es el único propietario. Por lo tanto, no hay forma de copiar foo.

En teoría, int [] debe ser un tipo bien definido, simple y agradable, 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 objetos inmutables se deriva de ese hecho.

Si devuelves referencias de una matriz o cadena, el mundo exterior puede modificar el contenido de ese objeto y, por lo tanto, convertirlo en un objeto mutable (modificable).

Los medios inmutables no se pueden cambiar y los mutables se pueden cambiar.

Los objetos son diferentes a los primitivos en Java. Los primitivos 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 .

Mutable las instancias se pasan por referencia.

Immutable las instancias se pasan por valor.

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

  1. Cree un acceso directo a txtfile y pásele el acceso directo, o
  2. Tome una copia para txtfile y pásele una copia.

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) tienen permisos para modificar el contenido del archivo.

En el segundo modo, devuelto txtfile 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).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top