Me encanta el "patrón" inmutable debido a sus puntos fuertes, y en el pasado he encontrado beneficioso diseñar sistemas con tipos de datos inmutables (algunos, la mayoría o incluso todos). A menudo, cuando lo hago, me encuentro escribiendo menos errores y la depuración es mucho más fácil.
Sin embargo, mis compañeros en general evitan ser inmutables. No tienen ninguna experiencia (ni mucho menos), sin embargo, escriben objetos de datos de la manera clásica: miembros privados con un captador y un establecedor para cada miembro. Entonces, por lo general, sus constructores no toman argumentos, o tal vez solo toman algunos argumentos por conveniencia. Muy a menudo, la creación de un objeto se ve así:
Foo a = new Foo();
a.setProperty1("asdf");
a.setProperty2("bcde");
Tal vez lo hacen en todas partes. Tal vez ni siquiera definen un constructor que tome esas dos cadenas, sin importar cuán importantes sean. Y tal vez no cambien el valor de esas cadenas más tarde y nunca lo necesiten. Claramente, si esas cosas son ciertas, el objeto estaría mejor diseñado como inmutable, ¿verdad? (el constructor toma las dos propiedades, no establece en absoluto).
¿Cómo decide si un tipo de objeto debe diseñarse como inmutable? ¿Existe un buen conjunto de criterios para juzgarlo?
Actualmente estoy debatiendo si cambiar algunos tipos de datos en mi propio proyecto a inmutables, pero tendría que justificarlo ante mis pares, y los datos en los tipos podrían cambiar (MUY raramente), momento en el que, por supuesto, puede cambiar de la manera inmutable (cree uno nuevo, copiando las propiedades del objeto antiguo, excepto las que desea cambiar). Pero no estoy seguro de si esto es solo mi amor por los inmutables que se muestran, o si existe una necesidad real / beneficio de ellos.
fuente
Respuestas:
Parece que te estás acercando al revés. Uno debería por defecto ser inmutable. Solo haga que un objeto sea mutable si absolutamente tiene que hacerlo / simplemente no puede hacerlo funcionar como un objeto inmutable.
fuente
El principal beneficio de los objetos inmutables es garantizar la seguridad del hilo. En un mundo donde múltiples núcleos e hilos son la norma, este beneficio se ha vuelto muy importante.
Pero usar objetos mutables es muy conveniente. Funcionan bien, y siempre que no los esté modificando desde subprocesos separados y comprenda bien lo que está haciendo, son bastante confiables.
fuente
Hay dos formas principales de decidir si un objeto es inmutable.
a) Basado en la naturaleza del objeto
Es fácil detectar estas situaciones porque sabemos que estos objetos no cambiarán después de su construcción. Por ejemplo, si tiene una
RequestHistory
entidad y, por naturaleza, las entidades de historia no cambian una vez que se construye. Estos objetos pueden diseñarse directamente como clases inmutables. Tenga en cuenta que Request Object es invaluable, ya que puede cambiar su estado y a quién está asignado, etc. con el tiempo, pero el historial de solicitudes no cambia. Por ejemplo, hubo un elemento de historial creado la semana pasada cuando pasó del estado enviado al asignado Y esta entidad de historial nunca puede cambiar. Así que este es un caso clásico inmutable.b) Según la elección del diseño, factores externos
Esto es similar al ejemplo de java.lang.String. Las cadenas pueden cambiar con el tiempo, pero por diseño, lo han hecho inmutable debido al almacenamiento en caché / grupo de cadenas / factores de concurrencia. De manera similar, el almacenamiento en caché / concurrencia, etc. puede desempeñar un buen papel en hacer que un objeto sea inmutable si el almacenamiento en caché / concurrencia y el rendimiento relacionado son vitales en la aplicación. Pero esta decisión debe tomarse con mucho cuidado después de analizar todos los impactos.
La principal ventaja de los objetos inmutables es que no están sujetos al patrón de caída de malezas. El objeto no detectará ningún cambio durante la vida útil y hace que la codificación y el mantenimiento sean muy fáciles.
fuente
Minimizar el estado de un programa es altamente beneficioso.
Pregúnteles si desean usar un tipo de valor mutable en una de sus clases para el almacenamiento temporal de una clase de cliente.
Si dicen que sí, pregunte por qué. El estado mutable no pertenece a un escenario como este. Forzarlos a crear el estado en el que realmente pertenece y hacer que el estado de sus tipos de datos sea lo más explícito posible son excelentes razones.
fuente
La respuesta es algo dependiente del idioma. Su código se parece a Java, donde este problema es lo más difícil posible. En Java, los objetos solo se pueden pasar por referencia y el clon está completamente roto.
No hay una respuesta simple, pero seguro que desea hacer que los objetos de valor pequeño sean inmutables. Java hizo correctamente las cadenas inmutables, pero incorrectamente hizo que la fecha y el calendario sean mutables.
Definitivamente, haga que los objetos de pequeño valor sean inmutables e implemente un constructor de copia. Olvídate de Cloneable, está tan mal diseñado que es inútil.
Para objetos de mayor valor, si es inconveniente hacerlos inmutables, entonces haga que sean fáciles de copiar.
fuente
Algo contra-intuitivo, no necesitar cambiar las cadenas más adelante es un argumento bastante bueno de que no importa si los objetos son inmutables o no. El programador ya los está tratando como efectivamente inmutables, ya sea que el compilador lo haga cumplir o no.
La inmutabilidad generalmente no duele, pero tampoco siempre ayuda. La manera fácil de saber si su objeto podría beneficiarse de la inmutabilidad es si alguna vez necesita hacer una copia del objeto o adquirir un mutex antes de cambiarlo . Si nunca cambia, la inmutabilidad realmente no te compra nada, y a veces hace las cosas más complicadas.
Usted hacer un buen punto sobre el riesgo de la construcción de un objeto en un estado no válido, pero eso es realmente una cuestión independiente de la inmutabilidad. Un objeto puede ser mutable y siempre en un estado válido después de la construcción.
La excepción a esa regla es que, dado que Java no admite parámetros con nombre ni parámetros predeterminados, a veces puede resultar difícil diseñar una clase que garantice un objeto válido simplemente usando constructores sobrecargados. No tanto con el caso de dos propiedades, pero también hay algo que decir sobre la coherencia, si ese patrón es frecuente con clases similares pero más grandes en otras partes de su código.
fuente
Object
características ocultas ", luego cambiar un objeto directamente no sería semánticamente diferente de sobrescribir la referencia con una referencia a un nuevo objeto que fue idéntico pero para el cambio indicado. Una de las mayores debilidades semánticas en Java, en mi humilde opinión, es que no tiene ningún medio por el cual el código pueda indicar que una variable solo debe ser la única referencia no efímera a algo; las referencias solo deberían ser pasables a métodos que posiblemente no puedan conservar una copia después de que regresen.Es posible que tenga una visión de nivel demasiado bajo de esto y probablemente porque estoy usando C y C ++, lo que no hace que sea tan sencillo hacer que todo sea inmutable, pero veo tipos de datos inmutables como un detalle de optimización para escribir más funciones eficientes desprovistas de efectos secundarios y poder proporcionar funciones muy fácilmente como sistemas de deshacer y edición no destructiva.
Por ejemplo, esto podría ser enormemente costoso:
... si
Mesh
no está diseñado para ser una estructura de datos persistente y, en cambio, era un tipo de datos que requería copiarlo por completo (que podría abarcar gigabytes en algún escenario) incluso si todo lo que vamos a hacer es cambiar un parte de ella (como en el escenario anterior donde solo modificamos las posiciones de los vértices).Entonces es cuando busco la inmutabilidad y diseño la estructura de datos para permitir que las partes no modificadas se copien poco a poco y se cuente la referencia, para permitir que la función anterior sea razonablemente eficiente sin tener que copiar en profundidad mallas enteras mientras aún puedo escribir el La función está libre de efectos secundarios, lo que simplifica drásticamente la seguridad del hilo, la seguridad de la excepción, la capacidad de deshacer la operación, aplicarla de forma no destructiva, etc.
En mi caso, es demasiado costoso (al menos desde el punto de vista de la productividad) hacer que todo sea inmutable, por lo que lo guardo para las clases que son demasiado caras para copiar por completo. Esas clases suelen ser estructuras de datos pesadas como mallas e imágenes y generalmente utilizo una interfaz mutable para expresarles cambios a través de un objeto "generador" para obtener una nueva copia inmutable. Y no estoy haciendo tanto para tratar de lograr garantías inmutables en el nivel central de la clase, sino para ayudarme a usar la clase en funciones que pueden estar libres de efectos secundarios. Mi deseo de hacer
Mesh
inmutable arriba no es directamente hacer que las mallas sean inmutables, sino permitir fácilmente escribir funciones sin efectos secundarios que ingresen una malla y generen una nueva sin pagar una memoria masiva y un costo computacional a cambio.Como resultado, solo tengo 4 tipos de datos inmutables en toda mi base de código, y todas son estructuras de datos pesadas, pero las uso en gran medida para ayudarme a escribir funciones que están libres de efectos secundarios. Esta respuesta podría ser aplicable si, como yo, está trabajando en un idioma que no hace que sea tan fácil hacer que todo sea inmutable. En ese caso, puede cambiar su enfoque hacia hacer que la mayor parte de sus funciones eviten los efectos secundarios, en ese momento es posible que desee hacer que las estructuras de datos seleccionadas sean inmutables, tipos PDS, como un detalle de optimización para evitar las costosas copias completas. Mientras tanto, cuando tengo una función como esta:
... entonces no tengo la tentación de hacer que los vectores sean inmutables ya que son lo suficientemente baratos como para copiarlos por completo. No se puede obtener una ventaja de rendimiento al convertir los vectores en estructuras inmutables que pueden copiar partes poco modificadas. Dichos costos superarían el costo de simplemente copiar todo el vector.
fuente
Si Foo solo tiene dos propiedades, entonces es fácil escribir un constructor que tome dos argumentos. Pero supongamos que agrega otra propiedad. Luego tienes que agregar otro constructor:
En este punto, todavía es manejable. Pero, ¿y si hay 10 propiedades? No quieres un constructor con 10 argumentos. Puede usar el patrón de construcción para construir instancias, pero eso agrega complejidad. En este momento, estarás pensando "¿por qué no agregué setters para todas las propiedades como hacen las personas normales"?
fuente