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.
object-oriented
immutability
ahsteele
fuente
fuente
Respuestas:
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".
fuente
Point
clase 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.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.
fuente
final
,const
etc. requiere un poco de esfuerzo extra ... a menos que configure una plantilla de código :-)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
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).
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?
fuente
Relationship(a, b)
; en el momento de crear la relación, ambas entidadesa
yb
ya 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.R
es elRelationship(a,b)
y ambosa
yb
son inmutables, nia
tampocob
obstaculicen una referencia aR
. Para que esto funcione, la referencia tendría que almacenarse en otro lugar (como una clase estática). ¿Estoy entendiendo tu intención correctamente?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.
fuente
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.
fuente
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.
fuente
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.
fuente
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
AllTrucks
contiene 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 variableBobsTruck
puede 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 queAllTrucks
ahora 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
BobsTruck
contiene una referencia directa al objeto # 24601, a menos que el código que actualizaAllTrucks
también se actualiceBobsTruck
, dejará de representar el estado actual del camión de Bob. Tenga en cuenta además que, a menos queBobsTruck
se almacene en alguna forma de objeto mutable, la única forma en que el código que actualizaAllTrucks
podría actualizarlo sería si el código se programara explícitamente para hacerlo.Si se quiere poder usar
BobsTruck
para observar el estado del camión de Bob mientras se mantienen todos los objetos inmutables, se podría tenerBobsTruck
una función inmutable que, dado el valor queAllTrucks
tiene 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 en
AllTrucks
y 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 utilizaCompareExchange
para actualizarAllTrucks
solo si no ha cambiado y responde a un errorCompareExchange
regenerando 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, un
CompareExchange
el 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.fuente
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 .
fuente
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:
La gente está usando objetos mutables, por lo que yo entiendo, porque todavía están mezclando OOP con programación procesal imperativa.
fuente
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 :
Puede mitigar el n. ° 1 y el n. ° 2 utilizando anotaciones como
@ConstructorProperties
y creando otro objeto de generador mutable (generalmente fluido) para crear el objeto inmutable.fuente
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.
fuente
¿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.
fuente
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.
fuente
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 .
fuente
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.
fuente
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.
fuente
new ReportBuilder().font("Arial").orientation("landscape").build()
withFont
devuelva aReport
.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:
fuente
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.
fuente
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:
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.
fuente
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:
fuente
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.
fuente
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.
fuente
final
en realidad es aproximadamente 1/4 de un paso hacia la inmutabilidad. No veo por qué lo mencionas.final
. Sacarlo a colación en este contexto evoca una combinación realmente extraña definal
"mutable", cuando el objetivofinal
es prevenir ciertos tipos de mutación. (Por cierto, no mi