oop - son - principios inmutables
¿Qué es la inmutabilidad y por qué debería preocuparme por ello? (15)
Inmutabilidad
En pocas palabras, la memoria es inmutable cuando no se modifica después de inicializarse.
Los programas escritos en lenguajes imperativos como C, Java y C # pueden manipular datos en memoria a voluntad. Un área de memoria física, una vez reservada, puede ser modificada en todo o en parte por un hilo de ejecución en cualquier momento durante la ejecución del programa. De hecho, los lenguajes imperativos fomentan esta forma de programación.
Escribir programas de esta manera ha sido increíblemente exitoso para aplicaciones de subproceso único. Sin embargo, a medida que el desarrollo de aplicaciones modernas avanza hacia múltiples hilos de operación simultáneos dentro de un solo proceso, se introduce un mundo de problemas potenciales y complejidad.
Cuando solo hay un hilo de ejecución, puede imaginarse que este único hilo "posee" todos los datos en la memoria y, por lo tanto, puede manipularlo a voluntad. Sin embargo, no existe un concepto implícito de propiedad cuando se trata de múltiples hilos de ejecución.
En cambio, esta carga recae sobre el programador que debe hacer grandes esfuerzos para garantizar que las estructuras en memoria estén en un estado constante para todos los lectores. Las construcciones de bloqueo se deben usar en medida cuidadosa para prohibir que un hilo vea datos mientras se está actualizando por otro hilo. Sin esta coordinación, un hilo inevitablemente consumiría datos que estaban solo a la mitad de su actualización. El resultado de tal situación es impredecible y a menudo catastrófico. Además, hacer que el bloqueo funcione correctamente en el código es notoriamente difícil y cuando se hace mal puede paralizar el rendimiento o, en el peor de los casos, bloqueos de casos que detienen la ejecución irrecuperable.
El uso de estructuras de datos inmutables alivia la necesidad de introducir un bloqueo complejo en el código. Cuando se garantiza que una sección de la memoria no cambiará durante la vida útil de un programa, múltiples lectores podrán acceder a la memoria simultáneamente. No es posible que observen esos datos particulares en un estado inconsistente.
Muchos lenguajes de programación funcionales, como Lisp, Haskell, Erlang, F # y Clojure, fomentan las estructuras de datos inmutables por su propia naturaleza. Es por esta razón que están disfrutando de un resurgimiento de interés a medida que avanzamos hacia un desarrollo de aplicaciones de subprocesos múltiples cada vez más complejo y arquitecturas de computadoras de muchas computadoras.
Estado
El estado de una aplicación simplemente puede considerarse como el contenido de toda la memoria y los registros de la CPU en un punto determinado en el tiempo.
Lógicamente, el estado de un programa se puede dividir en dos:
- El estado del montón
- El estado de la pila de cada hilo de ejecución
En entornos administrados como C # y Java, un hilo no puede acceder a la memoria de otro. Por lo tanto, cada hilo ''posee'' el estado de su pila. Se puede pensar que la pila contiene variables locales y parámetros del tipo de valor ( struct
) y las referencias a los objetos. Estos valores están aislados de los subprocesos externos.
Sin embargo, los datos en el montón se pueden compartir entre todos los hilos, por lo tanto, se debe tener cuidado para controlar el acceso concurrente. Todas las instancias de objeto de tipo de referencia ( class
) se almacenan en el montón.
En OOP, el estado de una instancia de una clase está determinado por sus campos. Estos campos se almacenan en el montón y, por lo tanto, se puede acceder desde todos los subprocesos. Si una clase define métodos que permiten que los campos se modifiquen después de que el constructor finalice, entonces la clase es mutable (no inmutable). Si los campos no se pueden cambiar de ninguna manera, entonces el tipo es inmutable. Es importante tener en cuenta que una clase con un campo mutable no es necesariamente inmutable. Por ejemplo, en C #, simplemente porque un campo de tipo List<object>
se define como de readonly
, el contenido real de la lista puede modificarse en cualquier momento.
Al definir un tipo como verdaderamente inmutable, su estado puede considerarse congelado y, por lo tanto, el tipo es seguro para el acceso por múltiples hilos.
En la práctica, puede ser inconveniente definir todos sus tipos como inmutables. Modificar el valor a en un tipo inmutable puede implicar un poco de copia de memoria. Algunos lenguajes hacen que este proceso sea más fácil que otros, pero de cualquier forma la CPU terminará haciendo un trabajo extra. Muchos factores contribuyen a determinar si el tiempo dedicado a copiar la memoria supera el impacto de las contenciones de bloqueo.
Se han realizado muchas investigaciones sobre el desarrollo de estructuras de datos inmutables, como listas y árboles. Al usar tales estructuras, digamos una lista, la operación ''agregar'' devolverá una referencia a una nueva lista con el nuevo elemento agregado. Las referencias a la lista anterior no muestran ningún cambio y aún tienen una vista coherente de los datos.
He leído un par de artículos sobre la inmutabilidad pero todavía no sigo el concepto muy bien.
Hice un hilo aquí recientemente que mencionó la inmutabilidad, pero como este es un tema en sí mismo, ahora estoy haciendo un hilo dedicado.
Mencioné en el hilo pasado que pensé que la inmutabilidad es el proceso de hacer que un objeto sea solo de lectura y le dé poca visibilidad. Otro miembro dijo que realmente no tenía nada que ver con eso. Esta página (parte de una serie ) usa un ejemplo de una clase / estructura inmutable y usa solo conceptos de lectura y otros para bloquearlo.
¿Cuál es exactamente la definición de estado en el caso de este ejemplo? El estado es un concepto que realmente no he captado.
Desde la perspectiva de una guía de diseño, una clase inmutable debe ser una que no acepte la entrada del usuario y realmente solo devuelva valores.
Según entiendo, cualquier objeto que simplemente devuelva información debe ser inmutable y estar "bloqueado", ¿verdad? Entonces, si quiero devolver la hora actual en una clase dedicada con ese único método, debería usar un tipo de referencia, ya que funcionará como una referencia del tipo y, por lo tanto, me beneficio de la inmutabilidad.
¿Qué es la inmutabilidad?
- La inmutabilidad se aplica principalmente a objetos (cadenas, matrices, una clase de Animal personalizada)
- Normalmente, si hay una versión inmutable de una clase, también está disponible una versión mutable. Por ejemplo, Objective-C y Cocoa definen tanto una clase NSString (inmutable) como una clase NSMutableString.
- Si un objeto es inmutable, no puede ser cambiado después de haber sido creado (básicamente de solo lectura). Se podría pensar que "solo el constructor puede cambiar el objeto".
Esto no tiene nada que ver directamente con la entrada del usuario; ni siquiera su código puede cambiar el valor de un objeto inmutable. Sin embargo, siempre puedes crear un nuevo objeto inmutable para reemplazarlo. Aquí hay un ejemplo de pseudocódigo; tenga en cuenta que en muchos idiomas simplemente puede hacer myString = "hello";
en lugar de usar un constructor como lo hice a continuación, pero lo incluí para mayor claridad:
String myString = new ImmutableString("hello");
myString.appendString(" world"); // Can''t do this
myString.setValue("hello world"); // Can''t do this
myString = new ImmutableString("hello world"); // OK
Usted menciona "un objeto que simplemente devuelve información"; esto no lo convierte automáticamente en un buen candidato para la inmutabilidad. Los objetos inmutables tienden a devolver siempre el mismo valor con el que fueron construidos, por lo que me inclino a decir que la hora actual no sería la ideal ya que eso cambia a menudo. Sin embargo, puede tener una clase MomentOfTime que se crea con una marca de tiempo específica y siempre devuelve esa marca de tiempo en el futuro.
Beneficios de la inmutabilidad
Si pasa un objeto a otra función / método, no debería preocuparse de si ese objeto tendrá el mismo valor después de que la función regrese. Por ejemplo:
String myString = "HeLLo WoRLd"; String lowercasedString = lowercase(myString); print myString + " was converted to " + lowercasedString;
¿Qué pasa si la implementación de
lowercase()
cambió myString cuando estaba creando una versión en minúsculas? La tercera línea no le daría el resultado que deseaba. Por supuesto, una buena funciónlowercase()
no haría esto, pero se garantiza este hecho si myString es inmutable. Como tal, los objetos inmutables pueden ayudar a imponer buenas prácticas de programación orientadas a objetos.Es más fácil hacer un objeto inmutable seguro para subprocesos
- Potencialmente, simplifica la implementación de la clase (bueno si eres el que escribe la clase)
Estado
Si tuviera que tomar todas las variables de instancia de un objeto y escribir sus valores en papel, ese sería el estado de ese objeto en ese momento dado. El estado del programa es el estado de todos sus objetos en un momento dado. El estado cambia rápidamente con el tiempo; un programa necesita cambiar de estado para poder seguir funcionando.
Los objetos inmutables, sin embargo, tienen un estado fijo a lo largo del tiempo. Una vez creado, el estado de un objeto inmutable no cambia aunque el estado del programa en su conjunto sí lo haga. Esto hace que sea más fácil hacer un seguimiento de lo que está sucediendo (y ver otros beneficios más arriba).
"... ¿Por qué debería preocuparme por eso?"
Un ejemplo práctico es la concatenación repetitiva de cadenas. En .NET por ejemplo:
string SlowStringAppend(string [] files)
{
// Declare an string
string result="";
for (int i=0;i<files.length;i++)
{
// result is a completely new string equal to itself plus the content of the new
// file
result = result + File.ReadAllText(files[i]);
}
return result;
}
string EfficientStringAppend(string [] files)
{
// Stringbuilder manages a internal data buffer that will only be expanded when absolutely necessary
StringBuilder result=new SringBuilder();
for (int i=0;i<files.length;i++)
{
// The pre-allocated buffer (result) is appended to with the new string
// and only expands when necessary. It doubles in size each expansion
// so need for allocations become less common as it grows in size.
result.Append(File.ReadAllText(files[i]));
}
return result.ToString();
}
Desafortunadamente, el uso del primer enfoque de función (lento) todavía se usa comúnmente. La comprensión de la inmutabilidad deja muy claro por qué el uso de StringBuilder es tan importante.
¿Por qué inmutabilidad?
Son menos propensos a errores y son más seguros.
Las clases inmutables son más fáciles de diseñar, implementar y usar que las clases mutables.
Los objetos inmutables son seguros para subprocesos, por lo que no hay problemas de sincronización.
Los objetos inmutables son buenas teclas Map y Set elements, ya que estos normalmente no cambian una vez creados.
La inmutabilidad hace que escribir, usar y razonar sobre el código sea más fácil (el invariante de clase se establece una vez y luego no se modifica).
La inmutabilidad facilita la paralelización del programa ya que no hay conflictos entre los objetos.
El estado interno del programa será uniforme incluso si tiene excepciones.
Las referencias a objetos inmutables se pueden almacenar en caché, ya que no van a cambiar (es decir, en Hashing proporciona operaciones rápidas).
Ver mi blog para una respuesta más detallada:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html
Buena pregunta.
Multi-threading. Si todos los tipos son inmutables, entonces las condiciones de carrera no existen y puedes lanzar tantas hebras al código como desees.
Obviamente, no se puede lograr tanto sin mutabilidad, salvo cálculos complejos, por lo que generalmente se necesita cierta capacidad de mutabilidad para crear un software empresarial funcional. Sin embargo, vale la pena reconocer dónde debe encontrarse la inmutabilidad, como cualquier cosa transaccional.
Busque la programación funcional y el concepto de pureza para obtener más información sobre la filosofía. Cuanto más almacene en la pila de llamadas (los parámetros que está transfiriendo a los métodos) en lugar de ponerlos a disposición a través de referencias como colecciones u objetos disponibles estáticamente, cuanto más puro sea su programa y menos propenso sea a las condiciones de carrera. Con más núcleos múltiples en estos días, este tema es más importante.
Además, la inmutabilidad reduce la cantidad de posibilidades en el programa, lo que reduce la complejidad potencial y el potencial de errores.
Déjame agregar una cosa más. Además de todo lo mencionado anteriormente, también quieres inmutabilidad para:
- objetos de valor (a veces también conocidos como objetos de datos o pojo)
- estructuras (en C # /. NET) - vea la pregunta de sobre el boxeo
Hacer las cosas inmutables evita una gran cantidad de errores comunes.
Por ejemplo, un Estudiante nunca debería tener su cambio de # estudiante en ellos. Si no proporciona una forma de establecer la variable (y hacerla const, o final, o lo que sea que soporte su lenguaje), puede aplicarla en tiempo de compilación.
Si las cosas son mutables y no quieres que cambien cuando las pases, debes hacer una copia defensiva que apruebes. Luego, si el método / función que llama cambia la copia del artículo, el original quedará intacto.
Hacer las cosas inmutables significa que no tienes que recordar (o tomar el tiempo / memoria) para hacer copias defensivas.
Si realmente trabajas en ello, y piensas en cada variable que hagas, verás que la gran mayoría (normalmente tengo un 90-95%) de tus variables nunca cambia una vez que reciben un valor. Hacer esto hace que los programas sean más fáciles de seguir y reduce la cantidad de errores.
Para responder a su pregunta sobre estado, indique los valores que tienen las variables de un "objeto" (ya sea una clase o una estructura). Si tomó un estado de "objeto" de persona serían cosas como color de ojos, color de cabello, longitud de cabello, etc. ... algunos de esos (por ejemplo, el color de ojos) no cambian mientras que otros, como la longitud del cabello cambian.
La inmutabilidad se trata de valores, y los valores son sobre hechos. Algo tiene valor si no se puede cambiar, porque si se puede cambiar algo, significa que no se puede conectar ningún valor específico. El objeto se inicializó con el estado A y durante la ejecución del programa se mutó al estado B y al estado C. Significa que el objeto no representa un único valor específico sino que es solo un contenedor, una abstracción en un lugar de la memoria, nada más. No puede confiar en ese contenedor, no puede creer que este contenedor tenga el valor que usted debería tener.
Vayamos al ejemplo: imaginemos que en el código se crea una instancia de la clase Libro.
Book bookPotter = new Book();
bookPotter.setAuthor(''J.K Rowling'');
bookPotter.setTitle(''Harry Potter'');
Esta instancia tiene algunos campos establecidos como autor y título. Todo está bien, pero en alguna parte del código se usan los setters.
Book bookLor = bookPotter; // only reference pass
bookLor.setAuthor(''J.R.R Tolkien'');
bookLor.setTitle(''Lords of The Rings'');
No se deje engañar por un nombre de variable diferente, realmente es la misma instancia. El código está usando setters en la misma instancia nuevamente. Significa que bookPotter nunca fue realmente el libro de Harry Potter, bookPotter es solo un puntero al lugar donde se encuentra el libro desconocido. Dicho esto, parece que es más un estante que el libro. ¿Qué confianza puedes tener para tal objeto? ¿Es el libro de Harry Potter o el libro de LoR o ninguno de los dos?
La instancia mutable de una clase es solo un puntero a un estado desconocido con las características de la clase.
¿Cómo entonces evitar la mutación? Es bastante fácil en las reglas:
- construir objeto con estado deseado a través de constructor o constructor
- no crear setters para el estado encapsulado del objeto
- no cambie ningún estado encapsulado del objeto en ninguno de sus métodos
Estas pocas reglas permitirán tener objetos más predecibles y más confiables. Regrese a nuestro ejemplo y reserve siguiendo las reglas anteriores:
Book bookPotter = new Book(''J.K Rowling'', ''Harry Potter'');
Book bookLor = new Book(''J.R.R Tolkien'', ''Lord of The Rings'');
Todo se establece durante la fase de construcción, en este caso constructor, pero para estructuras más grandes puede ser un generador. No existen setters en los objetos, el libro no puede mutar a uno diferente. En tal caso, BookPotter representa el valor del libro de Harry Potter y puede estar seguro de que esto es un hecho inmutable.
Si está interesado en un alcance más amplio de inmutabilidad, en este artículo medio se trata más sobre el tema en relación con JavaScript - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310 .
Las cosas que son inmutables nunca cambian. Las cosas mutables pueden cambiar. Las cosas mutables mutan. Las cosas inmutables parecen cambiar pero en realidad crean una nueva cosa mutable.
Por ejemplo, aquí hay un mapa en Clojure
(def imap {1 "1" 2 "2"})
(conj imap [3 "3"])
(println imap)
La primera línea crea un nuevo mapa de Clojure inmutable. La segunda línea une 3 y "3" al mapa. Esto puede parecer como si estuviera modificando el mapa anterior, pero en realidad está devolviendo un nuevo mapa con 3 "3" agregados. Este es un excelente ejemplo de inmutabilidad. Si este hubiera sido un mapa mutable, simplemente habría agregado 3 "3" directamente al mismo mapa anterior. La tercera línea imprime el mapa
{3 "3", 1 "1", 2 "2"}
La inmutabilidad ayuda a mantener el código limpio y seguro. Esta y otras razones explican por qué los lenguajes de programación funcionales tienden a inclinarse hacia la inmutabilidad y menos estado.
Mira, no he leído los enlaces que has publicado.
Sin embargo, aquí está mi entendimiento.
Todos los programas tienen algún conocimiento de sus datos (estado), que pueden cambiar ya sea por entrada de usuario / cambios externos, etc.
Las variables (valores que cambian) se mantienen para mantener el estado. Inmutable significa algunos datos que no cambian. Se puede decir que es lo mismo que readonly o constant de alguna manera (se puede ver de esa manera).
AFAIK, la programación funcional tiene cosas inmutables (es decir, no se puede usar la asignación a una variable que contenga el valor. Lo que se puede hacer es crear otra variable que pueda contener el valor original + cambios).
.net tiene una clase de cadena, que es un ejemplo.
es decir, no puede modificar la cadena en su lugar
cadena s = "hola"; Puedo escribir al remplazar ("el", "a"); Pero esto no modificará el contenido de la variable s.
Lo que puedo hacer es s = s.Replace ("el", "a");
Esto creará una nueva variable y asignará su valor a s (sobreescribiendo el contenido de s).
Los expertos pueden corregir los errores si los tengo, según entiendo.
EDIT: Immutable = Unassignable una vez que tiene algún valor y no puede ser reemplazado en su lugar (¿quizás?)
No puede cambiar un objeto inmutable, por lo tanto debe reemplazarlo .... "para cambiarlo". es decir, reemplazar y luego descartar. "Reemplazar" en este sentido significa cambiar el puntero de una ubicación de memoria (del valor anterior) a otra (para el nuevo valor).
Tenga en cuenta que al hacerlo, ahora usamos memoria adicional. Algunos por el valor anterior, otros por el nuevo valor. También tenga en cuenta que algunas personas se confunden porque miran el código, como por ejemplo:
string mystring = "inital value";
mystring = "new value";
System.Console.WriteLine(mystring); // Outputs "new value";
y piensen para sí mismos, "¡pero lo estoy cambiando, miren ahí, en blanco y negro! ¡Mistring saca ''nuevo valor'' ... creí que dijiste que no podía cambiarlo!"
Pero en realidad bajo el capó, lo que está sucediendo es esta asignación de nueva memoria, es decir, mystring ahora apunta a una dirección y espacio de memoria diferente. "Inmutable" en este sentido, no se refiere al valor de mystring sino a la memoria utilizada por la variable mystring para almacenar su valor.
En ciertos idiomas, la memoria que almacena el valor anterior debe limpiarse manualmente, es decir, el programador debe liberarla explícitamente ... y recuerde hacerlo. En otros idiomas, esta es una característica automática del lenguaje, es decir, la recolección de basura en .Net.
Uno de los lugares en los que esto realmente se arruina es que el uso de memoria se realiza en bucles altamente iterativos, especialmente con cadenas como en la publicación de Ashs. Digamos que está construyendo una página HTML en un bucle iterativo, donde constantemente anexó el siguiente bloque HTML a la última y, solo para patadas, estaba haciendo esto en un servidor de alto volumen. Esta asignación constante de "memoria de valor nuevo" puede volverse costosa rápidamente y, en última instancia, fatal si la "memoria de valores antiguos" no se limpia correctamente.
Otro problema es que algunas personas suponen que cosas como recolección de basura (GC) suceden inmediatamente. Pero no es así Hay varias optimizaciones que ocurren de modo que la recolección de basura se establece durante los períodos más inactivos. Por lo tanto, puede haber un retraso significativo entre cuando la memoria se marca como descartada y cuando el recolector de basura la libera realmente ... por lo que puede sufrir picos de uso de memoria grandes si simplemente difiere el problema al GC.
Si el GC no tiene la posibilidad de funcionar antes de que se quede sin memoria, entonces las cosas no necesariamente caerán como en otros idiomas que no tienen recolección automática de basura. En cambio, el GC se activará como el proceso de mayor prioridad para liberar la memoria descartada, sin importar qué tan mal sea el momento, y convertirse en un proceso de bloqueo mientras limpia las cosas. Obviamente, esto no es genial.
Así que, básicamente, debe tener en cuenta estos elementos y buscar en la documentación de los idiomas que está utilizando las mejores prácticas / patrones que le permiten evitar / mitigar este riesgo.
Al igual que en la publicación de Ashs, en .Net y con cadenas, la práctica recomendada es utilizar la clase mutable StringBuilder, en lugar de las clases de cadenas inmutables cuando se trata de la necesidad de cambiar constantemente el valor de las cadenas.
Otros idiomas / tipos también tendrán sus propias soluciones.
Para simplificar: una vez que crea un objeto inmutable, no hay forma de cambiar el contenido de ese objeto. Los ejemplos de objetos inmutables de .Net son String y Uri.
Cuando modifica una cadena, simplemente obtiene una nueva cadena. La cadena original no cambiará. Un Uri tiene solo propiedades de solo lectura y no hay métodos disponibles para cambiar el contenido del Uri.
Los casos en que los objetos inmutables son importantes son varios y en la mayoría de los casos tienen que ver con la seguridad. El Uri es un buen ejemplo aquí. (Por ejemplo, no desea que un Uri sea modificado por algún código que no sea de confianza). Esto significa que puede pasar una referencia a un objeto inmutable sin tener que preocuparse de que el contenido cambie alguna vez.
Espero que esto ayude.
Perdón, ¿por qué la inmutabilidad impide las condiciones de carrera (en este ejemplo, escribir después de los riesgos de lectura)?
shared v = Integer(3)
v = Integer(v.value() + 1) # in parallel
Un ejemplo de los posibles beneficios de rendimiento que ofrecen los objetos inmutables está disponible en la API de WPF. Una clase base común de muchos tipos WPF es Freezable
.
Varios ejemplos de WPF sugieren que la congelación de objetos (que los hace inmutables en tiempo de ejecución) puede mejorar significativamente el rendimiento de la aplicación, ya que no se requiere el bloqueo ni la copia.
Personalmente, desearía que el concepto de inmutabilidad fuera más fácil de expresar en el lenguaje que uso más a menudo, C #. Hay un modificador de readonly
disponible para los campos. Me gustaría ver también un modificador de readonly
en los tipos que solo se permitiría para los tipos que solo tienen campos de solo lectura que son de solo lectura. Esencialmente, esto significa que se debería inyectar todo el estado en el momento de la construcción, y que todo el gráfico de objetos se congelaría. Imagino que si estos metadatos fueran intrínsecos al CLR, entonces podrían usarse fácilmente para optimizar el análisis de basura para GC.
Un objeto inmutable es algo que puede asumir con seguridad que no va a cambiar; tiene la propiedad importante de que todos los que lo usan pueden suponer que están viendo el mismo valor.
La inmutabilidad generalmente también significa que puedes pensar que el objeto es un "valor" y que no existe una diferencia efectiva entre las copias idénticas del objeto y el objeto mismo.