Eu tenho alguns objetos grandes (mais de 3 campos) que podem e devem ser imutáveis. Cada vez que encontro esse caso, tendo a criar abominações de construtor com longas listas de parâmetros.
Não parece certo, é difícil de usar e a legibilidade é prejudicada.
É ainda pior se os campos forem algum tipo de coleção, como listas. Um simples addSibling(S s)
facilitaria muito a criação do objeto, mas tornaria o objeto mutável.
O que vocês usam nesses casos?
Estou usando Scala e Java, mas acho que o problema é agnóstico de linguagem, desde que a linguagem seja orientada a objetos.
Soluções em que posso pensar:
- "Abominações de construtor com longas listas de parâmetros"
- O Padrão Builder
java
oop
scala
immutability
Malax
fonte
fonte
Respostas:
Bem, você quer um objeto mais fácil de ler e imutável depois de criado?
Acho que uma interface fluente FEITA CORRETAMENTE ajudaria você.
Ficaria assim (exemplo puramente composto):
Escrevi "CONCLUÍDO CORRETAMENTE" em negrito porque a maioria dos programadores de Java erram nas interfaces fluentes e poluem seus objetos com o método necessário para construir o objeto, o que é completamente errado.
O truque é que apenas o método build () realmente cria um Foo (portanto, seu Foo pode ser imutável).
FooFactory.create () , ondeXXX (..) e comXXX (..) todos criam "algo diferente".
Essa outra coisa pode ser uma FooFactory, aqui está uma maneira de fazer isso ....
Sua FooFactory se pareceria com isto:
fonte
Foo
objeto, em vez de ter um separadoFooFactory
.FooImpl
construtor com 8 parâmetros. Qual é a melhoria?immutable
e teria medo de que as pessoas reutilizassem objetos de fábrica porque pensam que é. Quer dizer:FooFactory people = FooFactory.create().withType("person"); Foo women = people.withGender("female").build(); Foo talls = people.tallerThan("180m").build();
ondetalls
agora conteria apenas mulheres. Isso não deveria acontecer com uma API imutável.No Scala 2.8, você pode usar parâmetros nomeados e padrão, bem como o
copy
método em uma classe de caso. Aqui está um exemplo de código:fonte
Bem, considere isso no Scala 2.8:
Isso tem sua cota de problemas, é claro. Por exemplo, tente fazer
espouse
eOption[Person]
, e então casar duas pessoas entre si. Não consigo pensar em uma maneira de resolver isso sem recorrer a umprivate var
e / ou umprivate
construtor mais uma fábrica.fonte
Aqui estão mais algumas opções:
Opção 1
Torne a própria implementação mutável, mas separe as interfaces que ela expõe como mutáveis e imutáveis. Isso é retirado do design da biblioteca Swing.
opção 2
Se o seu aplicativo contém um grande, mas predefinido conjunto de objetos imutáveis (por exemplo, objetos de configuração), você pode considerar o uso da estrutura Spring .
fonte
Isso ajuda a lembrar que existem diferentes tipos de imutabilidade . Para o seu caso, acho que a imutabilidade do "picolé" funcionará muito bem:
Portanto, você inicializa seu objeto e, em seguida, define um sinalizador de "congelamento" de algum tipo, indicando que não é mais gravável. De preferência, você ocultaria a mutação atrás de uma função para que a função ainda fosse pura para clientes que consomem sua API.
fonte
clone()
para derivar novas instâncias.freeze()
método, as coisas podem ficar feias.Você também pode fazer com que os objetos imutáveis exponham métodos que se parecem com modificadores (como addSibling), mas permite que eles retornem uma nova instância. É isso que as coleções imutáveis do Scala fazem.
A desvantagem é que você pode criar mais instâncias do que o necessário. Também é aplicável apenas quando existem configurações válidas intermediárias (como algum nó sem irmãos, o que está ok na maioria dos casos), a menos que você não queira lidar com objetos parcialmente construídos.
Por exemplo, uma borda de gráfico que não tem destino ainda não é uma borda de gráfico válida.
fonte
Considere quatro possibilidades:
Para mim, cada um dos 2, 3 e 4 é adaptado a uma situação diferente. O primeiro é difícil de amar, pelos motivos citados pelo OP, e geralmente é um sintoma de um design que sofreu algum deslocamento e precisa de alguma refatoração.
O que estou listando como (2) é bom quando não há estado por trás da 'fábrica', enquanto (3) é o projeto escolhido quando há estado. Pego-me usando (2) em vez de (3) quando não quero me preocupar com threads e sincronização e não preciso me preocupar em amortizar algumas configurações caras na produção de muitos objetos. (3), por outro lado, é acionado quando o trabalho real vai para a construção da fábrica (configuração de um SPI, leitura de arquivos de configuração, etc).
Por fim, a resposta de outra pessoa mencionou a opção (4), onde você tem muitos pequenos objetos imutáveis e o padrão preferível é obter notícias dos antigos.
Observe que não sou membro do 'fã-clube de padrões' - claro, algumas coisas valem a pena imitar, mas me parece que eles assumem uma vida inútil quando as pessoas lhes dão nomes e chapéus engraçados.
fonte
Outra opção potencial é refatorar para ter menos campos configuráveis. Se grupos de campos só funcionam (principalmente) uns com os outros, reúna-os em seus próprios pequenos objetos imutáveis. Os construtores / construtores desse objeto "pequeno" devem ser mais gerenciáveis, assim como o construtor / construtor desse objeto "grande".
fonte
Eu uso C # e essas são minhas abordagens. Considerar:
Opção 1. Construtor com parâmetros opcionais
Usado como, por exemplo
new Foo(5, b: new Bar(whatever))
. Não para versões Java ou C # anteriores a 4.0. mas ainda vale a pena mostrar, pois é um exemplo de como nem todas as soluções são agnósticas de idioma.Opção 2. Construtor tomando um único objeto de parâmetro
Exemplo de uso:
C # do 3.0 em diante torna isso mais elegante com a sintaxe do inicializador de objeto (semanticamente equivalente ao exemplo anterior):
Opção 3:
redesenhe sua classe para não precisar de um número tão grande de parâmetros. Você pode dividir suas responsabilidades em várias classes. Ou passe parâmetros não para o construtor, mas apenas para métodos específicos, sob demanda. Nem sempre viável, mas quando for, vale a pena fazer.
fonte