Tengo algunos objetos grandes (más de 3 campos) que pueden y deben ser inmutables. Cada vez que me encuentro con ese caso tiendo a crear abominaciones de constructores con largas listas de parámetros.
No se siente bien, es difícil de usar y la legibilidad se ve afectada.
Es incluso peor si los campos son algún tipo de colección como listas. Un simple addSibling(S s)
facilitaría mucho la creación del objeto pero hace que el objeto sea mutable.
¿Qué usan ustedes en tales casos?
Estoy en Scala y Java, pero creo que el problema es agnóstico del lenguaje siempre que el lenguaje esté orientado a objetos.
Soluciones en las que puedo pensar:
- "Abominaciones de constructores con largas listas de parámetros"
- El patrón del constructor
java
oop
scala
immutability
Malax
fuente
fuente
Respuestas:
Bueno, ¿quieres un objeto más fácil de leer e inmutable una vez creado?
Creo que una interfaz fluida CORRECTAMENTE HECHA te ayudaría.
Se vería así (ejemplo puramente inventado):
Escribí "CORRECTAMENTE HECHO" en negrita porque la mayoría de los programadores de Java se equivocan en las interfaces fluidas y contaminan su objeto con el método necesario para construir el objeto, lo cual, por supuesto, es completamente incorrecto.
El truco es que solo el método build () realmente crea un Foo (por lo tanto, tu Foo puede ser inmutable).
FooFactory.create () , dondeXXX (..) y withXXX (..) todos crean "algo más".
Que algo más puede ser un FooFactory, aquí hay una forma de hacerlo ...
Tu FooFactory se vería así:
fuente
Foo
objeto, en lugar de tener una por separadoFooFactory
.FooImpl
constructor con 8 parámetros. ¿Cuál es la mejora?immutable
, y tendría miedo de que la gente reutilice objetos de fábrica porque creen que lo es. Quiero decir:FooFactory people = FooFactory.create().withType("person"); Foo women = people.withGender("female").build(); Foo talls = people.tallerThan("180m").build();
dondetalls
ahora solo contendría mujeres. Esto no debería suceder con una API inmutable.En Scala 2.8, puede usar parámetros con nombre y predeterminados, así como el
copy
método en una clase de caso. Aquí hay un código de ejemplo:fuente
Bueno, considere esto en Scala 2.8:
Esto tiene su parte de problemas, por supuesto. Por ejemplo, intente hacer
espouse
yOption[Person]
, y luego casar a dos personas entre sí. No puedo pensar en una forma de resolver eso sin recurrir a unprivate var
y / o unprivate
constructor más una fábrica.fuente
Aquí hay un par de opciones más:
Opción 1
Haga que la implementación en sí sea mutable, pero separe las interfaces que expone a mutables e inmutables. Esto se toma del diseño de la biblioteca Swing.
opcion 2
Si su aplicación contiene un conjunto grande pero predefinido de objetos inmutables (por ejemplo, objetos de configuración), puede considerar usar el marco Spring .
fuente
Es útil recordar que existen diferentes tipos de inmutabilidad . Para su caso, creo que la inmutabilidad de "paleta" funcionará muy bien:
Así que inicializas tu objeto, luego colocas un indicador de "congelación" de algún tipo que indica que ya no se puede escribir. Preferiblemente, escondería la mutación detrás de una función para que la función siga siendo pura para los clientes que consumen su API.
fuente
clone()
para derivar nuevas instancias.freeze()
método, pero no lo hace, las cosas podrían ponerse feas.También puede hacer que los objetos inmutables expongan métodos que parezcan mutantes (como addSibling) pero dejar que devuelvan una nueva instancia. Eso es lo que hacen las inmutables colecciones Scala.
La desventaja es que puede crear más instancias de las necesarias. También solo es aplicable cuando existen configuraciones intermedias válidas (como algún nodo sin hermanos, lo cual está bien en la mayoría de los casos) a menos que no desee tratar con objetos parcialmente construidos.
Por ejemplo, un borde de gráfico que aún no tiene destino no es un borde de gráfico válido.
fuente
Considere cuatro posibilidades:
Para mí, cada uno de 2, 3 y 4 se adapta a una situación diferente. El primero es difícil de amar, por las razones citadas por el OP, y generalmente es un síntoma de un diseño que ha sufrido algunos problemas y necesita una refactorización.
Lo que estoy enumerando como (2) es bueno cuando no hay un estado detrás de la 'fábrica', mientras que (3) es el diseño de elección cuando hay un estado. Me encuentro usando (2) en lugar de (3) cuando no quiero preocuparme por los hilos y la sincronización, y no necesito preocuparme por amortizar una configuración costosa sobre la producción de muchos objetos. (3), por otro lado, se solicita cuando se realiza un trabajo real en la construcción de la fábrica (configuración desde un SPI, lectura de archivos de configuración, etc.).
Finalmente, la respuesta de otra persona mencionó la opción (4), donde tiene muchos pequeños objetos inmutables y el patrón preferible es obtener noticias de los antiguos.
Tenga en cuenta que no soy miembro del 'club de fans de patrones'; claro, vale la pena emular algunas cosas, pero me parece que adquieren una vida inútil una vez que la gente les da nombres y sombreros divertidos.
fuente
Otra opción potencial es refactorizar para tener menos campos configurables. Si los grupos de campos solo funcionan (en su mayoría) entre sí, reúnalos en su propio pequeño objeto inmutable. Los constructores / constructores de ese objeto "pequeño" deberían ser más manejables, al igual que el constructor / constructor de este objeto "grande".
fuente
Yo uso C #, y estos son mis enfoques. Considerar:
Opción 1. Constructor con parámetros opcionales
Usado como por ejemplo
new Foo(5, b: new Bar(whatever))
. No para las versiones de Java o C # anteriores a la 4.0. pero vale la pena mostrarlo, ya que es un ejemplo de cómo no todas las soluciones son independientes del idioma.Opción 2. Constructor tomando un único objeto de parámetro
Ejemplo de uso:
C # de 3.0 en adelante lo hace más elegante con la sintaxis del inicializador de objetos (semánticamente equivalente al ejemplo anterior):
Opción 3:
Rediseñe su clase para que no necesite una cantidad tan grande de parámetros. Podría dividir sus responsabilidades en varias clases. O pase los parámetros no al constructor, sino solo a métodos específicos, bajo demanda. No siempre es viable, pero cuando lo es, vale la pena hacerlo.
fuente