Si los objetos inmutables son buenos, ¿por qué la gente sigue creando objetos mutables? [cerrado]


250

Si los objetos inmutables¹ son buenos, simples y ofrecen beneficios en la programación concurrente, ¿por qué los programadores siguen creando objetos mutables²?

Tengo cuatro años de experiencia en programación Java y, como lo veo, lo primero que hace la gente después de crear una clase es generar captadores y establecedores en el IDE (lo que lo hace mutable). ¿Existe una falta de conciencia o podemos evitar el uso de objetos mutables en la mayoría de los escenarios?


¹ objeto inmutable es un objeto cuyo estado no puede ser modificado después de su creación.
² Objeto mutable es un objeto que se puede modificar después de crearlo.


42
Creo que, aparte de razones legítimas (como se menciona a continuación por Péter), "desarrollador perezoso" es una razón más común que "desarrollador estúpido". Y antes de "desarrollador estúpido" también hay "desarrollador desinformado".
Joachim Sauer

201
Por cada programador / blogger evangélico hay 1000 ávidos lectores de blogs que se reinventan de inmediato y adoptan las últimas técnicas. Por cada uno de ellos, hay 10,000 programadores con la nariz puesta en la piedra de moler, trabajando un día y sacando el producto a la calle. Esos tipos están usando técnicas probadas y confiables que les han funcionado durante años. Esperan hasta que las nuevas técnicas sean ampliamente adoptadas y muestren beneficios reales antes de adoptarlas. No los llames estúpidos, y son cualquier cosa menos flojos, llámalos "ocupados".
Binario Worrier

22
@BinaryWorrier: los objetos inmutables no son una "cosa nueva". Puede que no se hayan usado mucho para los objetos de dominio, pero Java y C # los tuvieron desde el principio. Además: "perezoso" no siempre es una mala palabra, algunos tipos de "perezoso" son una ventaja absoluta para un desarrollador.
Joachim Sauer

11
@Joachim: Creo que es bastante obvio que "perezoso" se usó en su sentido peyorativo anterior :) Además, los objetos inmutables (como el cálculo de Lambda y la POO en el pasado, sí, soy tan viejo) no necesitan ser nuevos para convertirse de repente en el sabor del mes . No estoy argumentando que son algo malo (no lo son), o que no tienen su lugar (obviamente lo hacen), simplemente sean fáciles con la gente porque no han escuchado la última Buena Palabra y convertido como fervientemente uno mismo (sin culparlo por el comentario "perezoso", sé que trató de mitigarlo).
Binario Worrier

116
-1, los objetos inmutables no son 'buenos'. Simplemente más o menos apropiado para situaciones específicas. Cualquiera que te diga una técnica u otra es objetivamente 'bueno' o 'malo' sobre otra para todas las situaciones, te está vendiendo religión.
GrandmasterB

Respuestas:


326

Tanto los objetos mutables como los inmutables tienen sus propios usos, pros y contras.

Los objetos inmutables hacen la vida más simple en muchos casos. Son especialmente aplicables para los tipos de valor, donde los objetos no tienen una identidad, por lo que pueden reemplazarse fácilmente. Y pueden hacer que la programación concurrente sea más segura y limpia (la mayoría de los errores de concurrencia notoriamente difíciles de encontrar son en última instancia causados ​​por un estado mutable compartido entre hilos). Sin embargo, para objetos grandes y / o complejos, crear una nueva copia del objeto para cada cambio puede ser muy costoso y / o tedioso. Y para los objetos con una identidad distinta, cambiar los objetos existentes es mucho más simple e intuitivo que crear una copia nueva y modificada.

Piensa en un personaje del juego. En los juegos, la velocidad es la máxima prioridad, por lo que representar a los personajes de tu juego con objetos mutables probablemente hará que tu juego se ejecute significativamente más rápido que una implementación alternativa donde se genera una nueva copia del personaje del juego por cada pequeño cambio.

Además, nuestra percepción del mundo real se basa inevitablemente en objetos mutables. Cuando llena su automóvil con combustible en la estación de servicio, lo percibe como el mismo objeto todo el tiempo (es decir , se mantiene su identidad mientras cambia su estado ), no como si el automóvil viejo con un tanque vacío fuera reemplazado por uno nuevo instancias de automóviles que tienen su tanque gradualmente más y más lleno Entonces, cada vez que modelamos un dominio del mundo real en un programa, generalmente es más sencillo y sencillo implementar el modelo de dominio utilizando objetos mutables para representar entidades del mundo real.

Además de todas estas razones legítimas, por desgracia, la causa más probable por la cual las personas siguen creando objetos mutables es la inercia mental, también conocida como resistencia al cambio. Tenga en cuenta que la mayoría de los desarrolladores de hoy en día se han capacitado mucho antes de que la inmutabilidad (y el paradigma que lo contiene, la programación funcional) se haya "puesto de moda" en su esfera de influencia, y no mantienen sus conocimientos actualizados sobre las nuevas herramientas y métodos de nuestro comercio. de hecho, muchos de nosotros los humanos resistimos positivamente nuevas ideas y procesos. "He estado programando así durante nn años y no me importan las últimas estúpidas modas".


27
Eso es correcto. Especialmente en la programación GUI, los objetos mutables son muy útiles.
Florian Salihovic

23
No es solo resistencia, estoy seguro de que a muchos desarrolladores les encantaría probar lo último y lo mejor, pero ¿con qué frecuencia surgen nuevos proyectos en el entorno del desarrollador promedio donde pueden aplicar estas nuevas prácticas? Nadie puede o escribirá un proyecto de pasatiempo solo para probar un estado inmutable.
Steven Evers

29
Dos pequeñas advertencias a esto: (1) Toma el personaje del juego en movimiento. La Pointclase en .NET, por ejemplo, es inmutable, pero crear nuevos puntos como resultado de los cambios es fácil y, por lo tanto, asequible. Animar a un personaje inmutable puede hacerse muy barato desacoplando las "partes móviles" (pero sí, algún aspecto es mutable). (2) “objetos grandes y / o complejos” pueden muy bien ser inmutables. Las cadenas a menudo son grandes y generalmente se benefician de la inmutabilidad. Una vez reescribí una clase gráfica compleja para que sea inmutable, haciendo que el código sea más simple y más eficiente. En tales casos, tener un constructor mutable es la clave.
Konrad Rudolph

44
@KonradRudolph, buenos puntos, gracias. No quise descartar el uso de la inmutabilidad en objetos complejos, pero implementar tal clase de manera correcta y eficiente está lejos de ser una tarea trivial, y el esfuerzo adicional requerido puede no estar siempre justificado.
Péter Török

66
Haces un buen punto sobre el estado frente a la identidad. Es por eso que Rich Hickey (autor de Clojure) separó a los dos en Clojure. Se podría argumentar que el automóvil que tiene con 1/2 tanque de gasolina no es el mismo que el que tiene 1/4 de tanque de gasolina. Tienen la misma identidad, pero no son lo mismo, cada "tic" del tiempo de nuestra realidad crea un clon de cada objeto en nuestro mundo, nuestros cerebros simplemente los unen con una identidad común. Clojure tiene referencias, átomos, agentes, etc. para representar el tiempo. Y mapas, vectores y listas para el tiempo real.
Timothy Baldridge

130

Creo que todos se han perdido la respuesta más obvia. La mayoría de los desarrolladores crean objetos mutables porque la mutabilidad es la predeterminada en los lenguajes imperativos. La mayoría de nosotros tenemos mejores cosas que hacer con nuestro tiempo que modificar constantemente el código lejos de los valores predeterminados, más correcto o no. Y la inmutabilidad no es una panacea más que cualquier otro enfoque. Facilita algunas cosas, pero hace que otras sean mucho más difíciles, como algunas respuestas ya han señalado.


99
En la mayoría de los IDE modernos que conozco, se necesita prácticamente el mismo esfuerzo para generar solo captadores, que generar captadores y establecedores. Aunque es cierto que agregar final, constetc. requiere un poco de esfuerzo extra ... a menos que configure una plantilla de código :-)
Péter Török

10
@ PéterTörök no es solo el esfuerzo adicional: es el hecho de que tus compañeros codificadores querrán ahorcarte porque encuentran que tu estilo de codificación es muy ajeno a su experiencia. Eso también desalienta este tipo de cosas.
Onorio Catenacci

55
Eso podría superarse con más comunicación y educación, por ejemplo, es recomendable hablar o dar una presentación a los compañeros de equipo sobre un nuevo estilo de codificación antes de introducirlo en una base de código existente. Es cierto que un buen proyecto tiene un estilo de codificación común que no es solo una amalgama de los estilos de codificación favorecidos por cada miembro del proyecto (pasado y presente). Por lo tanto, introducir o cambiar modismos de codificación debería ser una decisión del equipo.
Péter Török

3
@ PéterTörök En un lenguaje imperativo, los objetos mutables son el comportamiento predeterminado. Si solo desea objetos inmutables, es mejor cambiar a un lenguaje funcional.
emory

3
@ PéterTörök Es un poco ingenuo suponer que incorporar la inmutabilidad en un programa no implica nada más que soltar todos los setters. Todavía necesita una forma para que el estado del programa cambie gradualmente, y para esto necesita constructores, o inmutabilidad de paletas, o proxies mutables, todo lo cual requiere aproximadamente mil millones de veces el esfuerzo que solo requiere la colocación de los setters.
Asad Saeeduddin el

49
  1. Hay un lugar para la mutabilidad. Los principios de diseño impulsados ​​por el dominio proporcionan una comprensión sólida de lo que debería ser mutable y lo que debería ser inmutable. Si lo piensa, se dará cuenta de que no es práctico concebir un sistema en el que cada cambio de estado a un objeto requiera la destrucción y la recomposición del mismo, y de cada objeto al que se hace referencia. Con sistemas complejos, esto podría conducir fácilmente a borrar y reconstruir completamente el gráfico de objetos de todo el sistema

  2. La mayoría de los desarrolladores no hacen nada donde los requisitos de rendimiento sean lo suficientemente significativos como para que tengan que centrarse en la concurrencia (o en muchos otros problemas que los informados consideran una buena práctica universalmente).

  3. Hay algunas cosas que simplemente no puede hacer con objetos inmutables, como tener relaciones bidireccionales. Una vez que establece un valor de asociación en un objeto, sus cambios de identidad. Por lo tanto, establece el nuevo valor en el otro objeto y también cambia. El problema es que la referencia del primer objeto ya no es válida, porque se ha creado una nueva instancia para representar el objeto con la referencia. Continuar esto solo resultaría en infinitas regresiones. Hice un pequeño estudio de caso después de leer su pregunta, así es como se ve. ¿Tiene un enfoque alternativo que permita dicha funcionalidad mientras mantiene la inmutabilidad?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF., Si tiene una opinión diferente sobre cómo mantener gráficos complejos con relaciones bidireccionales utilizando solo objetos inmutables, me encantaría escucharlo. (Supongo que podemos aceptar que una colección / matriz es un objeto)
smartcaveman

2
@AndresF., (1) Mi primera declaración no fue universal, por lo que no fue falsa. De hecho, proporcioné un ejemplo de código para explicar cómo era necesariamente cierto en ciertos casos que resultan ser muy comunes en el desarrollo de aplicaciones. (2) Las relaciones bidireccionales requieren mutabilidad, en general. No creo que Java sea el culpable. Como dije, me complacería evaluar cualquier alternativa constructiva que sugiera, pero en este punto sus comentarios se parecen mucho a "Está equivocado porque lo dije".
smartcaveman

14
@smartcaveman Acerca de (2), también estoy en desacuerdo: en general, una "relación bidireccional" es un concepto matemático ortogonal a la mutabilidad. Como generalmente se implementa en Java, requiere mutabilidad (estuve de acuerdo con usted en ese punto). Sin embargo, puedo pensar en una implementación alternativa: una clase de relación entre los dos objetos, con un constructor Relationship(a, b); en el momento de crear la relación, ambas entidades ay bya existen, y la relación en sí misma también es inmutable. No digo que este enfoque sea práctico en Java; solo que es posible.
Andres F.

44
@AndresF., Por lo tanto, en base a lo que está diciendo, si Res el Relationship(a,b)y ambos ay bson inmutables, ni atampoco bobstaculicen una referencia a R. Para que esto funcione, la referencia tendría que almacenarse en otro lugar (como una clase estática). ¿Estoy entendiendo tu intención correctamente?
smartcaveman

99
Es posible almacenar una relación bidireccional para datos inmutables como Chthulhu señala a través de la pereza. Aquí hay una manera de hacer esto: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

He estado leyendo "estructuras de datos puramente funcionales", y me ha hecho darme cuenta de que hay bastantes estructuras de datos que son mucho más fáciles de implementar utilizando objetos mutables.

Para implementar un árbol de búsqueda binario, debe devolver un nuevo árbol cada vez: su nuevo árbol habrá tenido que hacer una copia de cada nodo que se ha modificado (las ramas no modificadas se comparten). Para su función de inserción, esto no es tan malo, pero para mí, las cosas se volvieron bastante ineficientes rápidamente cuando comencé a trabajar en eliminar y reequilibrar.

La otra cosa a tener en cuenta es que puede pasar años escribiendo código orientado a objetos, y nunca darse cuenta de cuán terrible puede ser el estado mutable compartido, si su código no se ejecuta de una manera que exponga problemas de concurrencia.


3
¿Es este el libro de Okasaki?
Caracol mecánico

Sí. un poco seco pero un montón de buena información ...
Paul Sanwald

44
Es curioso, siempre pensé que el árbol rojo / negro de Okasakis era mucho más simple. 10 líneas más o menos. Supongo que la mayor ventaja es cuando realmente quieres mantener la versión anterior también.
Thomas Ahle

Si bien la última oración probablemente ha sido cierta en el pasado, no está claro si seguirá siendo cierta en el futuro, dadas las tendencias actuales del hardware, etc.
jk.

29

Desde mi punto de vista, eso es una falta de conciencia. Si observa otros lenguajes JVM conocidos (Scala, Clojure), los objetos mutables se ven raramente en el código y es por eso que la gente comienza a usarlos en escenarios donde el subproceso único no es suficiente.

Actualmente estoy aprendiendo Clojure y tengo un poco de experiencia en Scala (más de 4 años en Java también) y su estilo de codificación cambia debido a la conciencia del estado.


Quizás "conocido" en lugar de "popular" sería una mejor elección de palabra.
Den

Si, eso es correcto.
Florian Salihovic

55
+1: Estoy de acuerdo: después de aprender algunos Scala y Haskell, tiendo a usar final en Java y const en C ++ en todas partes. También uso objetos inmutables si es posible y, aunque todavía se necesitan objetos mutables con mucha frecuencia, es sorprendente la frecuencia con la que puede usar objetos inmutables.
Giorgio

55
Leí este comentario mío después de dos años y medio, y mi opinión ha cambiado a favor de la inmutabilidad. En mi proyecto actual (que está en Python) estamos usando objetos mutables muy raramente. Incluso nuestros datos persistentes son inmutables: creamos nuevos registros como resultado de algunas operaciones y eliminamos registros antiguos cuando ya no se necesitan, pero nunca actualizamos ningún registro en el disco. No hace falta decir que esto ha hecho que nuestra aplicación concurrente y multiusuario sea mucho más fácil de implementar y mantener hasta ahora.
Giorgio

13

Creo que un factor importante que contribuye ha sido ignorada: Java Beans dependen en gran medida de un estilo específico de mutación de objetos, y (especialmente teniendo en cuenta la fuente) Muy pocas personas parecen tomar esto como una (o incluso la ) ejemplo canónico de cómo todos Java Debería estar escrito.


44
+1, el patrón getter / setter se usa con demasiada frecuencia como algún tipo de implementación predeterminada después del primer análisis de datos.
Jaap

Este es probablemente un gran punto ... "porque así es como lo están haciendo todos los demás" ... así que debe ser correcto. Para un programa "Hello World" probablemente sea más fácil. Administrar los cambios de estado de los objetos a través de una serie de propiedades mutables ... eso es un poco más complicado que las profundidades de la comprensión de "Hello World". 20 años después, estoy muy sorprendido de que el pináculo de la programación del siglo XX sea escribir métodos getX y setX (qué tedioso) sobre cada atributo de un objeto sin estructura alguna. Está a solo un paso de acceder directamente a propiedades públicas con un 100% de mutabilidad.
Darrell Teague

12

Todos los sistemas Java empresariales en los que he trabajado en mi carrera utilizan Hibernate o Java Persistence API (JPA). Hibernate y JPA esencialmente dictan que su sistema use objetos mutables, porque la premisa de ellos es que detectan y guardan los cambios en sus objetos de datos. Para muchos proyectos, la facilidad de desarrollo que trae Hibernate es más convincente que los beneficios de los objetos inmutables.

Obviamente, los objetos mutables han existido durante mucho más tiempo que Hibernate, por lo que Hibernate probablemente no sea la "causa" original de la popularidad de los objetos mutables. Quizás la popularidad de los objetos mutables permitió que Hibernate floreciera.

Pero hoy en día, si muchos programadores junior cortan sus dientes en sistemas empresariales que usan Hibernate u otro ORM, entonces presumiblemente adoptarán el hábito de usar objetos mutables. Los marcos como Hibernate pueden estar consolidando la popularidad de los objetos mutables.


Excelente punto Para ser todo para todas las personas, tales marcos no tienen más remedio que caer al mínimo común denominador, usar proxies basados ​​en la reflexión y obtener / establecer su camino hacia la flexibilidad. Por supuesto, esto crea sistemas con pocas o ninguna regla de transición de estado o un medio común para implementarlas desafortunadamente. No estoy del todo seguro después de muchos proyectos, ahora qué es mejor para la conveniencia, extensibilidad y corrección. Tiendo a pensar en un híbrido. Calidad ORM dinámica pero con algunas definiciones para qué campos son obligatorios y qué cambios de estado deberían ser posibles.
Darrell Teague

9

Un punto importante aún no mencionado es que el hecho de que el estado de un objeto sea mutable hace posible que la identidad del objeto que encapsula ese estado sea inmutable.

Muchos programas están diseñados para modelar cosas del mundo real que son inherentemente mutables. Suponga que a las 12:51 am, alguna variable AllTruckscontiene una referencia al objeto # 451, que es la raíz de una estructura de datos que indica qué carga está contenida en todos los camiones de una flota en ese momento (12:51 am), y alguna variable BobsTruckpuede usarse para obtener una referencia al objeto # 24601 que apunta a un objeto que indica qué carga está contenida en el camión de Bob en ese momento (12:51 am). A las 12:52 a.m., algunos camiones (incluido el de Bob) se cargan y descargan, y las estructuras de datos se actualizan para que AllTrucksahora contengan una referencia a una estructura de datos que indique que la carga está en todos los camiones a las 12:52 a.m.

¿Qué debería pasar BobsTruck?

Si la propiedad de 'carga' de cada objeto de camión es inmutable, entonces el objeto # 24601 representará para siempre el estado que tenía el camión de Bob a las 12:51 am. Si BobsTruckcontiene una referencia directa al objeto # 24601, a menos que el código que actualiza AllTruckstambién se actualice BobsTruck, dejará de representar el estado actual del camión de Bob. Tenga en cuenta además que, a menos que BobsTruckse almacene en alguna forma de objeto mutable, la única forma en que el código que actualiza AllTruckspodría actualizarlo sería si el código se programara explícitamente para hacerlo.

Si se quiere poder usar BobsTruckpara observar el estado del camión de Bob mientras se mantienen todos los objetos inmutables, se podría tener BobsTruckuna función inmutable que, dado el valor que AllTruckstiene o tuvo en un momento determinado, generará el estado del camión de Bob en ese momento. Uno podría incluso tener un par de funciones inmutables, una de las cuales sería la anterior y la otra aceptaría una referencia a un estado de flota y un nuevo estado de camión, y devolvería una referencia a un nuevo estado de flota que coincidía con el viejo, excepto que la camioneta de Bob tendría el nuevo estado.

Desafortunadamente, tener que usar esa función cada vez que se quiere acceder al estado del camión de Bob podría ser bastante molesto y engorroso. Un enfoque alternativo sería decir que el objeto # 24601 representará siempre y para siempre (siempre y cuando alguien tenga una referencia a él) representará el estado actual del camión de Bob. El código que querrá acceder repetidamente al estado actual del camión de Bob no tendría que ejecutar alguna función que requiera mucho tiempo cada vez; simplemente podría hacer una función de búsqueda una vez para descubrir que el objeto # 24601 es el camión de Bob, y luego simplemente acceder a ese objeto cada vez que quiera ver el estado actual del camión de Bob.

Tenga en cuenta que el enfoque funcional no está exento de ventajas en un entorno de subproceso único, o en un entorno de subprocesos múltiples donde los subprocesos en su mayoría solo estarán observando datos en lugar de cambiarlos. Cualquier hilo de observación que copia la referencia de objeto contenida enAllTrucksy luego examina los estados de los camiones representados, de este modo verá el estado de todos los camiones a partir del momento en que tomó la referencia. Cada vez que un hilo de observador quiere ver datos más nuevos, simplemente puede volver a tomar la referencia. Por otro lado, tener todo el estado de la flota representado por un solo objeto inmutable impediría la posibilidad de que dos subprocesos actualicen diferentes camiones simultáneamente, ya que cada subproceso si se deja en sus propios dispositivos produciría un nuevo objeto de "estado de flota" que incluía El nuevo estado de su camión y los viejos estados de cada uno. Se puede asegurar la corrección si cada subproceso se utiliza CompareExchangepara actualizar AllTruckssolo si no ha cambiado y responde a un errorCompareExchangeregenerando su objeto de estado y reintentando la operación, pero si más de un hilo intenta una operación de escritura simultánea, el rendimiento generalmente será peor que si toda la escritura se hiciera en un solo hilo; cuantos más hilos intenten tales operaciones simultáneas, peor será el rendimiento.

Si los objetos individuales del camión son mutables pero tienen identidades inmutables , el escenario de subprocesos múltiples se vuelve más limpio. Solo se puede permitir que un hilo opere a la vez en un camión determinado, pero los hilos que operan en camiones diferentes podrían hacerlo sin interferencia. Si bien hay formas en que uno podría emular tal comportamiento incluso cuando usa objetos inmutables (por ejemplo, uno podría definir los objetos "AllTrucks" de modo que establecer el estado del camión que pertenece a XXX a SSS simplemente requeriría generar un objeto que dijera "A partir de [Tiempo], el estado del camión que pertenece a [XXX] ahora es [SSS]; el estado de todo lo demás es [Valor anterior de AllTrucks] ". Generar un objeto de este tipo sería lo suficientemente rápido que incluso en presencia de contención, unCompareExchangeel bucle no llevaría mucho tiempo. Por otro lado, el uso de dicha estructura de datos aumentaría sustancialmente la cantidad de tiempo requerida para encontrar el camión de una persona en particular. El uso de objetos mutables con identidades inmutables evita ese problema.


8

No hay correcto o incorrecto, solo depende de lo que prefiera. Hay una razón por la cual algunas personas prefieren idiomas que favorecen un paradigma sobre otro, y un modelo de datos sobre otro. Solo depende de tu preferencia y de lo que quieras lograr (y poder usar fácilmente ambos enfoques sin alienar a los fanáticos acérrimos de un lado u otro es un santo grial que algunos idiomas buscan).

Creo que la mejor y más rápida forma de responder a su pregunta es dirigiéndose a Pros y Contras de Inmutabilidad vs Mutabilidad .


7

Consulte esta publicación de blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Resume por qué los objetos inmutables son mejores que los mutables. Aquí hay una breve lista de argumentos:

  • 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é

La gente está usando objetos mutables, por lo que yo entiendo, porque todavía están mezclando OOP con programación procesal imperativa.


5

En Java, los objetos inmutables requieren un constructor que tomará todas las propiedades del objeto (o el constructor los crea a partir de otros argumentos o valores predeterminados). Esas propiedades deben marcarse como finales .

Hay cuatro problemas con esto que tienen que ver principalmente con el enlace de datos :

  1. Los metadatos de reflexión de Constructores Java no retienen los nombres de los argumentos.
  2. Los constructores (y métodos) de Java no tienen parámetros con nombre (también llamados etiquetas), por lo que se confunde con muchos parámetros.
  3. Al heredar otro objeto inmutable, debe llamarse el orden correcto de los constructores. Esto puede ser bastante complicado hasta el punto de simplemente rendirse y dejar uno de los campos no final.
  4. La mayoría de las tecnologías de enlace (como Spring MVC Data Binding, Hibernate, etc.) solo funcionarán con constructores predeterminados sin argumentos (esto se debe a que las anotaciones no siempre existieron).

Puede mitigar el n. ° 1 y el n. ° 2 utilizando anotaciones como @ConstructorPropertiesy creando otro objeto de generador mutable (generalmente fluido) para crear el objeto inmutable.


5

Me sorprende que nadie haya mencionado los beneficios de la optimización del rendimiento. Dependiendo del idioma, un compilador puede hacer un montón de optimizaciones cuando se trata de datos inmutables porque sabe que los datos nunca cambiarán. Se omiten todo tipo de cosas, lo que le brinda enormes beneficios de rendimiento.

También los objetos inmutables casi eliminan toda la clase de errores de estado.

No es tan grande como debería ser porque es más difícil, no se aplica a todos los idiomas y a la mayoría de las personas se les enseñó la codificación imperativa.

También he descubierto que la mayoría de los programadores son felices en su caja y a menudo son resistentes a las nuevas ideas que no entienden completamente. En general, a la gente no le gusta el cambio.

Recuerde también que el estado de la mayoría de los programadores es malo. La mayor parte de la programación que se realiza en la naturaleza es terrible y está causada por una falta de comprensión y política.


4

¿Por qué las personas usan alguna característica poderosa? ¿Por qué la gente usa metaprogramación, pereza o tipeo dinámico? La respuesta es conveniencia. El estado mutable es muy fácil. Es muy fácil actualizarlo en su lugar y el umbral del tamaño del proyecto donde el estado inmutable es más productivo para trabajar que el estado mutable es bastante alto, por lo que la elección no lo morderá por un tiempo.


4

Los lenguajes de programación están diseñados para ser ejecutados por computadoras. Todos los componentes básicos importantes de las computadoras (CPU, RAM, caché, discos) son mutables. Cuando no lo son (BIOS), son realmente inmutables y tampoco puedes crear nuevos objetos inmutables.

Por lo tanto, cualquier lenguaje de programación construido sobre objetos inmutables adolece de una brecha de representación en su implementación. Y para los primeros lenguajes como C, ese fue un gran obstáculo.


1

Sin objetos mutables no tienes estado. Es cierto que esto es bueno si puede administrarlo y si existe la posibilidad de que un objeto pueda ser referenciado desde más de un hilo. Pero el programa va a ser bastante aburrido. Una gran cantidad de software, particularmente servidores web, evita asumir la responsabilidad de los objetos mutables al eliminar la mutabilidad en bases de datos, sistemas operativos, bibliotecas de sistemas, etc. Como cuestión práctica, esto libera al programador de problemas de mutabilidad y crea web (y otros) Desarrollo asequible. Pero la mutabilidad sigue ahí.

En general, tiene tres tipos de clases: clases normales, no seguras para subprocesos, que deben protegerse y protegerse cuidadosamente; clases inmutables, que se pueden usar libremente; y clases mutables y seguras para subprocesos que se pueden usar libremente pero que se deben escribir con extremo cuidado. El primer tipo es el problemático, siendo los peores los que se consideran del tercer tipo. Por supuesto, el primer tipo son los fáciles de escribir.

Por lo general, termino con muchas clases normales y mutables que tengo que mirar con mucho cuidado. En una situación de múltiples hilos, la sincronización necesaria ralentiza todo incluso cuando puedo evitar un abrazo mortal. Por lo general, estoy haciendo copias inmutables de la clase mutable y entregándoselos a quien pueda usarla. Se necesita una nueva copia inmutable cada vez que el original muta, así que imagino que a veces puedo tener cientos de copias del original. Soy completamente dependiente de la recolección de basura.

En resumen, los objetos mutables que no son seguros para subprocesos están bien si no está usando múltiples subprocesos. (Pero el subprocesamiento múltiple se está iluminando en todas partes, ¡tenga cuidado!) De lo contrario, se pueden usar de forma segura si los restringe a las variables locales o las sincroniza rigurosamente. Si puede evitarlos utilizando el código probado de otras personas (DB, llamadas al sistema, etc.), hágalo. Si puede usar una clase inmutable, hágalo. Y creo que , en general , las personas no son conscientes de los problemas de subprocesos múltiples o están (sensiblemente) aterrorizados de ellos y utilizan todo tipo de trucos para evitar el subprocesamiento múltiple (o más bien, exigen la responsabilidad por otro lado).

Como PS, siento que los captadores y establecedores de Java se están yendo de las manos. Mira esto .


77
El estado inmutable sigue siendo el estado.
Jeremy Heiler

3
@JeremyHeiler: Es cierto, pero es el estado de algo que es mutable. Si nada muta, solo hay un estado, que es lo mismo que ningún estado.
RalphChapin

1

Mucha gente tenía buenas respuestas, así que quiero señalar algo que mencionó que fue muy observador y extremadamente cierto y que no se ha mencionado en ninguna otra parte aquí.

Crear automáticamente setters y getters es una idea horrible, horrible, sin embargo, es la primera forma en que las personas con mentalidad procesal intentan forzar a OO a su mentalidad. Los establecedores y captadores, junto con las propiedades, solo deben crearse cuando descubra que los necesita y no de forma predeterminada

De hecho, aunque necesita getters con bastante regularidad, la única forma en que los setters o las propiedades de escritura deberían existir en su código es a través de un patrón de generador donde se bloquean después de que el objeto se haya instanciado por completo.

Muchas clases son mutables después de la creación, lo cual está bien, simplemente no debería tener sus propiedades directamente manipuladas; en su lugar, se le debe pedir que manipule sus propiedades a través de llamadas a métodos con lógica comercial real en ellas (Sí, un setter es más o menos lo mismo cosa como manipular directamente la propiedad)

Ahora, esto tampoco se aplica al código / lenguajes de estilo "Scripting", sino al código que cree para otra persona y espere que otros lean repetidamente a lo largo de los años. He tenido que comenzar a hacer esa distinción últimamente porque disfruto mucho jugando con Groovy y hay una gran diferencia en los objetivos.


1

Los objetos mutables se usan cuando tiene que establecer valores múltiples después de crear una instancia del objeto.

No debería tener un constructor con, digamos, seis parámetros. En su lugar, modifica el objeto con métodos setter.

Un ejemplo de esto es un objeto Informe, con definidores de fuente, orientación, etc.

Para abreviar: los mutables son útiles cuando tiene mucho estado para establecer en un objeto y no sería práctico tener una firma de constructor muy larga.

EDITAR: Se puede usar el patrón de construcción para construir todo el estado del objeto.


3
Esto se presenta como si la mutabilidad y los cambios después de la creación de instancias fueran la única forma de establecer múltiples valores. En aras de la exhaustividad, tenga en cuenta que el patrón Builder ofrece capacidades iguales o incluso más potentes y no requiere uno para sacrificar la inmutabilidad. new ReportBuilder().font("Arial").orientation("landscape").build()
mosquito

1
"No sería práctico tener una firma de constructor muy larga": siempre puede agrupar parámetros en objetos más pequeños y pasar estos objetos como parámetros al constructor.
Giorgio

1
@cHao ¿Qué sucede si desea establecer 10 o 15 atributos? Tampoco parece bueno que un método llamado withFontdevuelva a Report.
Tulains Córdova

1
En cuanto a establecer 10 o 15 atributos, el código para hacerlo no sería incómodo si (1) Java supiera cómo eludir la construcción de todos esos objetos intermedios, y si (2) nuevamente, los nombres se estandarizaron. No es un problema con la inmutabilidad; Es un problema con Java que no sabe cómo hacerlo bien.
cHao

1
@cHao Hacer una cadena de llamadas para 20 atributos es feo. El código feo tiende a ser un código de baja calidad.
Tulains Córdova

1

Creo que el uso de objetos mutables proviene del pensamiento imperativo: se calcula un resultado cambiando el contenido de las variables mutables paso a paso (cálculo por efecto secundario).

Si piensa funcionalmente, desea tener un estado inmutable y representar estados posteriores de un sistema aplicando funciones y creando nuevos valores a partir de los antiguos.

El enfoque funcional puede ser más limpio y más robusto, pero puede ser muy ineficiente debido a la copia, por lo que desea recurrir a una estructura de datos compartida que modifique de forma incremental.

La compensación que considero más razonable es: Comience con objetos inmutables y luego cambie a objetos mutables si su implementación no es lo suficientemente rápida. Desde este punto de vista, el uso sistemático de objetos mutables desde el principio puede considerarse algún tipo de optimización prematura : elige la implementación más eficiente (pero también más difícil de entender y depurar) desde el principio.

Entonces, ¿por qué muchos programadores usan objetos mutables? En mi humilde opinión por dos razones:

  1. Muchos programadores han aprendido a programar usando un paradigma imperativo (de procedimiento u orientado a objetos), por lo tanto, la mutabilidad es su enfoque básico para definir la computación, es decir, no saben cuándo y cómo usar la inmutabilidad porque no están familiarizados con ella.
  2. Muchos programadores se preocupan por el rendimiento demasiado pronto, mientras que a menudo es más efectivo enfocarse primero en escribir un programa que sea funcionalmente correcto, y luego tratar de encontrar cuellos de botella y optimizarlo.

1
El problema no es solo la velocidad. Hay muchos tipos de construcciones útiles que simplemente no se pueden implementar sin usar tipos mutables. Entre otras cosas, la comunicación entre dos hilos requiere que, como mínimo absoluto, ambos deben tener una referencia a un objeto compartido en el que uno puede poner datos y el otro puede leerlo. Además, a menudo es semánticamente mucho más claro decir "Cambiar las propiedades P y Q de este objeto" que decir "Tomar este objeto, construir un nuevo objeto que sea igual excepto por el valor de P, y luego un nuevo objeto que es así, excepto por Q ".
supercat

1
No soy un experto en FP, pero la comunicación de hilos AFAIK (1) se puede lograr creando un valor inmutable en un hilo y leyéndolo en otro (de nuevo, AFAIK, este es el enfoque de Erlang) (2) AFAIK la notación para establecer una propiedad no cambia mucho (por ejemplo, en Haskell el valor del registro setProperty corresponde a Java record.setProperty (valor)), solo la semántica cambia porque el resultado de establecer una propiedad es un nuevo registro inmutable.
Giorgio

1
"ambos deben tener una referencia a un objeto compartido en el que uno puede poner datos y el otro puede leerlo": más precisamente, puede hacer que un objeto sea inmutable (todos los miembros const en C ++ o final en Java) y establecer todo el contenido en el constructor. Luego, este objeto inmutable se entrega del subproceso productor al subproceso consumidor.
Giorgio

1
(2) Ya he enumerado el rendimiento como una razón para no usar el estado inmutable. Con respecto a (1), por supuesto, el mecanismo que implementa la comunicación entre subprocesos necesita un estado mutable, pero puede ocultarlo de su modelo de programación; consulte, por ejemplo, el modelo de actor ( en.wikipedia.org/wiki/Actor_model ). Entonces, incluso si en un nivel inferior necesita mutabilidad para implementar la comunicación, puede tener un nivel de abstracción superior en el que se comunica entre hilos que envían objetos inmutables de un lado a otro.
Giorgio

1
No estoy seguro de entenderte completamente, pero incluso un lenguaje funcional puro donde cada valor es inmutable, hay una cosa mutable: el estado del programa, es decir, la pila del programa actual y los enlaces variables. Pero este es el entorno de ejecución. De todos modos, sugiero que discutamos esto en el chat en algún momento y luego borremos los mensajes de esta pregunta.
Giorgio

1

Sé que estás preguntando sobre Java, pero uso mutable vs inmutable todo el tiempo en el objetivo-c. Hay una matriz inmutable NSArray y una matriz mutable NSMutableArray. Estas son dos clases diferentes escritas específicamente de manera optimizada para manejar el uso exacto. Si necesito crear una matriz y nunca alterar su contenido, usaría NSArray, que es un objeto más pequeño y es mucho más rápido en lo que hace, en comparación con una matriz mutable.

Entonces, si crea un objeto Person que es inmutable, entonces solo necesita un constructor y captadores, por lo que el objeto será más pequeño y usará menos memoria, lo que a su vez hará que su programa sea realmente más rápido. Si necesita cambiar el objeto después de la creación, un objeto Persona mutable será mejor para que pueda cambiar los valores en lugar de crear un nuevo objeto.

Entonces: dependiendo de lo que planeas hacer con el objeto, elegir mutable frente a inmutable puede marcar una gran diferencia en lo que respecta al rendimiento.


1

Además de muchas otras razones dadas aquí, un problema es que los lenguajes convencionales no admiten bien la inmutabilidad. Al menos, se le penaliza por la inmutabilidad, ya que debe agregar palabras clave adicionales como const o final, y debe usar constructores ilegibles con muchos, muchos argumentos o patrones de construcción largos de código.

Eso es mucho más fácil en idiomas creados con la inmutabilidad en mente. Considere este fragmento de Scala para definir una clase Persona, opcionalmente con argumentos con nombre, y crear una copia con un atributo modificado:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

Entonces, si desea que los desarrolladores adopten la inmutabilidad, esta es una de las razones por las que podría considerar cambiar a un lenguaje de programación más moderno.


Esto no parece agregar nada sustancial sobre los puntos hechos y explicados en las 24 respuestas anteriores
mosquito

@gnat ¿Cuál de las respuestas anteriores señala que la mayoría de los idiomas convencionales no admiten decentemente la inmutabilidad? Creo que ese punto simplemente no se ha hecho (lo comprobé), pero en mi humilde opinión es un obstáculo bastante importante.
Hans-Peter Störr

este, por ejemplo, explica en profundidad este problema en Java. Y al menos otros 3 respuestas indirectamente se relacionan con ella
mosquito

0

Inmutable significa que no puede cambiar el valor, y mutable significa que puede cambiar el valor si piensa en términos de primitivas y objetos. Los objetos son diferentes a los primitivos en Java en que están construidos en tipos. Las primitivas se construyen en tipos como int, boolean y void.

Mucha gente piensa que las variables primitivas y de objetos 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. Consulte este enlace para obtener un ejemplo de código:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

Creo que una buena razón sería modelar "objetos" mutables "de la vida real", como las ventanas de interfaz. Recuerdo vagamente haber leído que el OOP fue inventado cuando alguien intentó escribir software para controlar alguna operación del puerto de carga.


1
Todavía no puedo encontrar dónde leí esto sobre los orígenes de OOP, pero según Wikipedia , algunos sistemas de información regional integrada de una gran empresa de transporte de contenedores OOCL están escritos en Smalltalk.
Alexey

-2

Java, en muchos casos exige objetos mutables, por ejemplo, cuando desea contar o devolver algo en un visitante o ejecutable, necesita variables finales, incluso sin subprocesos múltiples.


55
finalen realidad es aproximadamente 1/4 de un paso hacia la inmutabilidad. No veo por qué lo mencionas.
cHao

¿realmente leíste mi publicación?
Ralf H

¿Realmente leíste la pregunta? :) No tenía absolutamente nada que ver con final. Sacarlo a colación en este contexto evoca una combinación realmente extraña de final"mutable", cuando el objetivo finales prevenir ciertos tipos de mutación. (Por cierto, no mi
voto negativo

No digo que no tengas un punto válido aquí en alguna parte. Estoy diciendo que no lo estás explicando muy bien. Casi veo a dónde probablemente vas con eso, pero debes ir más allá. Como es, solo parece confundido.
cHao

1
En realidad, hay muy pocos casos en los que se requiere un objeto mutable . Hay casos en los que el código sería más claro, o en algunos casos más rápido, mediante el uso de objetos mutables. Pero tenga en cuenta que la gran mayoría de los patrones de diseño son básicamente una interpretación a medias de la programación funcional para lenguajes que no lo admiten de forma nativa. Parte divertida de esto es que la FP solo requiere mutabilidad en lugares muy selectos (es decir, donde deben ocurrir efectos secundarios), y esos lugares generalmente están muy limitados en número, tamaño y alcance.
cHao
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.