Veo los beneficios de hacer que los objetos en mi programa sean inmutables. Cuando realmente estoy pensando profundamente en un buen diseño para mi aplicación, a menudo llego naturalmente a que muchos de mis objetos son inmutables. A menudo llega al punto en que me gustaría tener todos mis objetos inmutables.
Esta pregunta aborda la misma idea, pero ninguna respuesta sugiere cuál es un buen enfoque para la inmutabilidad y cuándo usarla realmente. ¿Hay algunos buenos patrones de diseño inmutables? La idea general parece ser "hacer que los objetos sean inmutables a menos que sea absolutamente necesario que cambien", lo cual es inútil en la práctica.
Mi experiencia es que la inmutabilidad conduce mi código cada vez más al paradigma funcional y esta progresión siempre ocurre:
- Comienzo a necesitar estructuras de datos persistentes (en el sentido funcional) como listas, mapas, etc.
- Es extremadamente inconveniente trabajar con referencias cruzadas (por ejemplo, el nodo de árbol que hace referencia a sus hijos mientras que los niños hacen referencia a sus padres), lo que me hace no usar referencias cruzadas, lo que nuevamente hace que mis estructuras de datos y mi código sean más funcionales.
- La herencia se detiene para tener sentido y en su lugar empiezo a usar la composición.
- Todas las ideas básicas de OOP como la encapsulación comienzan a desmoronarse y mis objetos comienzan a parecerse a funciones.
En este punto, prácticamente ya no uso nada del paradigma OOP y puedo cambiar a un lenguaje puramente funcional. Por lo tanto, mi pregunta: ¿existe un enfoque coherente para un buen diseño OOP inmutable o es siempre el caso de que cuando llevas la idea inmutable a su máximo potencial, siempre terminas programando en un lenguaje funcional que ya no necesita nada del mundo OOP? ¿Hay alguna buena guía para decidir qué clases deberían ser inmutables y cuáles deberían permanecer mutables para garantizar que la POO no se desmorone?
Solo por conveniencia, daré un ejemplo. Tengamos ChessBoard
una colección inmutable de piezas de ajedrez inmutables (ampliando la clase abstractaPiece
) Desde el punto de vista de OOP, una pieza es responsable de generar movimientos válidos desde su posición en el tablero. Pero para generar los movimientos, la pieza necesita una referencia a su tablero, mientras que el tablero debe tener referencia a sus piezas. Bueno, hay algunos trucos para crear estas referencias cruzadas inmutables dependiendo de su lenguaje OOP, pero son difíciles de manejar, mejor no tener una pieza para hacer referencia a su tablero. Pero entonces la pieza no puede generar sus movimientos ya que no conoce el estado del tablero. Luego, la pieza se convierte en una estructura de datos que contiene el tipo de pieza y su posición. Luego puede usar una función polimórfica para generar movimientos para todo tipo de piezas. Esto se puede lograr perfectamente en la programación funcional, pero es casi imposible en OOP sin verificaciones de tipo de tiempo de ejecución y otras malas prácticas de OOP ... Entonces,
Respuestas:
No veo por qué no. Lo he estado haciendo durante años antes de que Java 8 funcionara de todos modos. ¿Has oído hablar de cadenas? Agradable e inmutable desde el principio.
He estado necesitando esos todo el tiempo también. Invalidar mis iteradores porque mutaste la colección mientras lo leía es simplemente grosero.
Las referencias circulares son un tipo especial de infierno. La inmutabilidad no te salvará de eso.
Bueno, aquí estoy contigo, pero no veo qué tiene que ver con la inmutabilidad. La razón por la que me gusta la composición no es porque me encanta el patrón de estrategia dinámica, sino porque me permite cambiar mi nivel de abstracción.
Me estremezco al pensar cuál es su idea de "OOP como encapsulación". Si se trata de captadores y establecedores, simplemente deje de llamar a esa encapsulación porque no lo es. Nunca fue Es una programación manual orientada a aspectos. Una oportunidad para validar y un lugar para poner un punto de interrupción es agradable, pero no es una encapsulación. La encapsulación está preservando mi derecho a no saber ni preocuparme por lo que sucede dentro.
Se supone que sus objetos parecen funciones. Son bolsas de funciones. Son bolsas de funciones que se mueven juntas y se redefinen juntas.
La programación funcional está de moda en este momento y la gente está arrojando algunas ideas falsas sobre la POO. No dejes que eso te confunda en creer que este es el final de la POO. Funcional y OOP pueden vivir juntos bastante bien.
La programación funcional es formal sobre las tareas.
OOP está siendo formal sobre los punteros de función.
Realmente eso. Dykstra nos dijo que
goto
era dañino, así que nos formamos al respecto y creamos una programación estructurada. Solo así, estos dos paradigmas tratan de encontrar formas de hacer las cosas mientras se evitan las trampas que surgen de hacer estas cosas problemáticas de manera casual.Déjame mostrarte algo:
f n (x)
Esa es una función. En realidad es un continuo de funciones:
f 1 (x)
f 2 (x)
...
f n (x)
¿Adivina cómo expresamos eso en los idiomas OOP?
n.f(x)
Ese poco
n
allí elige qué implementaciónf
se usa Y decide cuáles son algunas de las constantes usadas en esa función (lo que francamente significa lo mismo). Por ejemplo:f 1 (x) = x + 1
f 2 (x) = x + 2
Eso es lo mismo que proporcionan los cierres. Cuando los cierres se refieren a su alcance, los métodos de objeto se refieren a su estado de instancia. Los objetos pueden hacer cierres uno mejor. Un cierre es una sola función devuelta por otra función. Un constructor devuelve una referencia a una bolsa completa de funciones:
g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2
Sí lo adivinaste:
n.g(x)
f y g son funciones que cambian juntas y se mueven juntas. Entonces los metemos en la misma bolsa. Esto es realmente un objeto. Mantenerse
n
constante (inmutable) solo significa que es más fácil predecir qué harán cuando los llames.Ahora esa es solo la estructura. La forma en que pienso sobre OOP es un montón de pequeñas cosas que hablan con otras pequeñas cosas. Con suerte a un pequeño grupo selecto de pequeñas cosas. Cuando codifico me imagino como el objeto. Miro las cosas desde el punto de vista del objeto. Y trato de ser flojo para no sobrecargar el objeto. Tomo mensajes simples, trabajo un poco en ellos y envío mensajes simples solo a mis mejores amigos. Cuando termino con ese objeto, me meto en otro y miro las cosas desde su perspectiva.
Las Tarjetas de Responsabilidad de Clase fueron las primeras en enseñarme a pensar de esa manera. Hombre, estaba confundido acerca de ellos en ese entonces, pero maldita sea si todavía no son relevantes hoy.
Arg! De nuevo con las innecesarias referencias circulares.
Qué tal: A
ChessBoardDataStructure
convierte los cables xy en referencias de piezas. Esas piezas tienen un método que toma x, y y un particularChessBoardDataStructure
y lo convierte en una colección de nuevos y sorprendentesChessBoardDataStructure
s. Luego mete eso en algo que puede elegir el mejor movimiento. AhoraChessBoardDataStructure
puede ser inmutable y también lo pueden ser las piezas. De esta manera, solo tienes un peón blanco en la memoria. Solo hay varias referencias a él en las ubicaciones xy correctas. Orientado a objetos, funcional e inmutable.Espera, ¿ya no hablamos de ajedrez?
fuente
Los conceptos más útiles introducidos en la corriente principal por OOP, en mi opinión, son:
Todos estos beneficios también se pueden obtener sin los detalles de implementación tradicionales, como la herencia o incluso las clases. La idea original de Alan Kay de un "sistema orientado a objetos" utilizaba "mensajes" en lugar de "métodos", y estaba más cerca de Erlang que, por ejemplo, C ++. Mire Go, que elimina muchos detalles de implementación de OOP tradicionales, pero aún se siente razonablemente orientado a objetos.
Si usa objetos inmutables, aún puede usar la mayoría de los objetos tradicionales de OOP: interfaces, despacho dinámico, encapsulación. No necesita setters, y a menudo ni siquiera necesita getters para objetos más simples. También puede obtener los beneficios de la inmutabilidad: siempre está seguro de que un objeto no cambió mientras tanto, sin copia defensiva, sin carreras de datos, y es fácil hacer puros los métodos.
Observe cómo Scala intenta combinar la inmutabilidad y los enfoques de FP con OOP. Es cierto que no es el lenguaje más simple y elegante. Sin embargo, es razonablemente prácticamente exitoso. Además, eche un vistazo a Kotlin que proporciona muchas herramientas y enfoques para una mezcla similar.
El problema habitual al intentar un enfoque diferente al que los creadores del lenguaje tenían en mente hace N años es el "desajuste de impedancia" con la biblioteca estándar. OTOH, los ecosistemas Java y .NET actualmente tienen un soporte razonable de biblioteca estándar para estructuras de datos inmutables; por supuesto, también existen bibliotecas de terceros.
fuente