Se objetos imutáveisable são bons, simples e oferecem benefícios na programação simultânea, por que os programadores continuam criando objetos mutáveis²?
Tenho quatro anos de experiência em programação Java e, a meu ver, a primeira coisa que as pessoas fazem após criar uma classe é gerar getters e setters no IDE (tornando-o mutável). Existe uma falta de consciência ou podemos nos safar usando objetos mutáveis na maioria dos cenários?
¹ objeto imutável é um objeto cujo estado não pode ser modificado após sua criação.
² Objeto mutável é um objeto que pode ser modificado após a criação.
object-oriented
immutability
ahsteele
fonte
fonte
Respostas:
Objetos mutáveis e imutáveis têm seus próprios usos, prós e contras.
Objetos imutáveis de fato tornam a vida mais simples em muitos casos. Eles são especialmente aplicáveis aos tipos de valor, onde os objetos não têm uma identidade para que possam ser facilmente substituídos. E eles podem tornar a programação simultânea muito mais segura e limpa (a maioria dos bugs de concorrência notoriamente difíceis de encontrar são causados por estados mutáveis compartilhados entre threads). No entanto, para objetos grandes e / ou complexos, a criação de uma nova cópia do objeto para cada alteração pode ser muito dispendiosa e / ou entediante. E para objetos com uma identidade distinta, alterar objetos existentes é muito mais simples e intuitivo do que criar uma nova cópia modificada dele.
Pense em um personagem do jogo. Nos jogos, a velocidade é a principal prioridade; portanto, representar seus personagens com objetos mutáveis provavelmente fará com que seu jogo corra significativamente mais rápido do que uma implementação alternativa, onde uma nova cópia do personagem do jogo é gerada a cada pequena alteração.
Além disso, nossa percepção do mundo real é inevitavelmente baseada em objetos mutáveis. Quando você enche seu carro com combustível no posto de gasolina, você o percebe como o mesmo objeto o tempo todo (ou seja, sua identidade é mantida enquanto o estado está mudando) - não como se o carro antigo com um tanque vazio fosse substituído por novos consecutivos instâncias de carros com o tanque gradualmente cada vez mais cheio. Portanto, sempre que modelamos algum domínio do mundo real em um programa, geralmente é mais simples e fácil implementar o modelo de domínio usando objetos mutáveis para representar entidades do mundo real.
Além de todas essas razões legítimas, infelizmente, a causa mais provável pela qual as pessoas continuam criando objetos mutáveis é a inércia da mente, também conhecida como resistência à mudança. Observe que a maioria dos desenvolvedores de hoje foi treinada muito antes da imutabilidade (e o paradigma que a continha, a programação funcional) se tornou "moderna" em sua esfera de influência e não mantém seus conhecimentos atualizados sobre as novas ferramentas e métodos de nosso comércio - de fato, muitos de nós humanos resistimos positivamente a novas idéias e processos. "Venho programando assim há nn anos e não ligo para as últimas modas estúpidas!"
fonte
Point
classe no .NET, por exemplo, é imutável, mas a criação de novos pontos como resultado de alterações é fácil e, portanto, acessível. Animar um personagem imutável pode ser muito barato, dissociando as “partes móveis” (mas sim, algum aspecto é então mutável). (2) “objetos grandes e / ou complexos” podem muito bem ser imutáveis. As cordas geralmente são grandes e geralmente se beneficiam da imutabilidade. Uma vez, reescrevi uma classe gráfica complexa para ser imutável, tornando o código mais simples e mais eficiente. Nesses casos, ter um construtor mutável é a chave.Acho que todos vocês perderam a resposta mais óbvia. A maioria dos desenvolvedores cria objetos mutáveis porque a mutabilidade é o padrão em linguagens imperativas. A maioria de nós tem coisas melhores a fazer com o nosso tempo do que modificar constantemente o código dos padrões - mais corretos ou não. E a imutabilidade não é mais uma panacéia do que qualquer outra abordagem. Isso facilita algumas coisas, mas torna outras muito mais difíceis, como algumas respostas já apontaram.
fonte
final
,const
etc. leva um pouco de esforço extra ... a menos que você configurar um modelo de código :-)Há um lugar para a mutabilidade. Os princípios de design orientados a domínio fornecem uma sólida compreensão do que deve ser mutável e do que deve ser imutável. Se você pensar bem, perceberá que é impraticável conceber um sistema no qual toda mudança de estado em um objeto exija a destruição e a recomposição dele, e em todos os objetos que o referenciam. Com sistemas complexos, isso poderia facilmente levar a uma limpeza e reconstrução completas do gráfico de objetos do sistema inteiro
A maioria dos desenvolvedores não faz nada em que os requisitos de desempenho sejam significativos o suficiente para que precisem se concentrar na simultaneidade (ou em muitos outros problemas que são universalmente considerados boas práticas pelos informados).
Existem algumas coisas que você simplesmente não pode fazer com objetos imutáveis, como ter relacionamentos bidirecionais. Depois de definir um valor de associação em um objeto, ele muda de identidade. Então, você define o novo valor no outro objeto e ele também muda. O problema é que a referência do primeiro objeto não é mais válida, porque uma nova instância foi criada para representar o objeto com a referência. Continuar isso resultaria em regressões infinitas. Fiz um pequeno estudo de caso depois de ler sua pergunta, eis a aparência dela. Você tem uma abordagem alternativa que permita essa funcionalidade, mantendo a imutabilidade?
fonte
Relationship(a, b)
; no momento de criar o relacionamento, ambas as entidadesa
eb
já existem, e o próprio relacionamento também é imutável. Não estou dizendo que essa abordagem é prática em Java; apenas que é possível.R
é oRelationship(a,b)
e os doisa
eb
são imutáveis, nema
tampoucob
manterá uma referênciaR
. Para que isso funcione, a referência teria que ser armazenada em outro lugar (como uma classe estática). Estou entendendo sua intenção corretamente?Eu tenho lido "estruturas de dados puramente funcionais", e isso me fez perceber que existem algumas estruturas de dados que são muito mais fáceis de implementar usando objetos mutáveis.
Para implementar uma árvore de pesquisa binária, você deve retornar uma nova árvore sempre: Sua nova árvore terá que fazer uma cópia de cada nó que foi modificado (as ramificações não modificadas são compartilhadas). Para sua função de inserção, isso não é muito ruim, mas para mim, as coisas ficaram bastante ineficientes rapidamente quando comecei a trabalhar em excluir e reequilibrar.
A outra coisa a perceber é que você pode passar anos escrevendo código orientado a objetos e nunca perceber como o estado mutável compartilhado pode ser terrível, se o seu código não for executado de maneira a expor problemas de simultaneidade.
fonte
Do meu ponto de vista, isso é falta de consciência. Se você observar outras linguagens conhecidas da JVM (Scala, Clojure), objetos mutáveis são vistos raramente no código e é por isso que as pessoas começam a usá-los em cenários em que a segmentação única não é suficiente.
Atualmente, estou aprendendo Clojure e tenho uma pequena experiência em Scala (4 anos ou mais em Java também) e seu estilo de codificação muda devido à conscientização do estado.
fonte
Eu acho que um dos principais fatores contribuintes foi ignorado: o Java Beans depende muito de um estilo específico de objetos mutantes e (especialmente considerando a fonte) muitas pessoas parecem entender isso como um exemplo canônico (ou mesmo o ) canônico de como todo Java deve ser escrito.
fonte
Todo sistema Java corporativo em que trabalhei na minha carreira usa o Hibernate ou o Java Persistence API (JPA). O Hibernate e o JPA essencialmente determinam que o seu sistema use objetos mutáveis, porque a premissa deles é que eles detectam e salvam alterações nos objetos de dados. Para muitos projetos, a facilidade de desenvolvimento que o Hibernate oferece é mais atraente do que os benefícios de objetos imutáveis.
Obviamente, objetos mutáveis existem há muito mais tempo que o Hibernate, portanto o Hibernate provavelmente não é a 'causa' original da popularidade de objetos mutáveis. Talvez a popularidade de objetos mutáveis tenha permitido ao Hibernate florescer.
Hoje, porém, se muitos programadores juniores cortam seus dentes em sistemas corporativos usando o Hibernate ou outro ORM, presumivelmente, eles adquirem o hábito de usar objetos mutáveis. Estruturas como o Hibernate podem estar consolidando a popularidade de objetos mutáveis.
fonte
Um ponto importante ainda não mencionado é que ter o estado de um objeto mutável torna possível a identidade do objeto que encapsula esse estado imutável.
Muitos programas são projetados para modelar coisas do mundo real que são inerentemente mutáveis. Suponha que, às 12h51, alguma variável
AllTrucks
contenha uma referência ao objeto nº 451, que é a raiz de uma estrutura de dados que indica qual carga está contida em todos os caminhões de uma frota naquele momento (12h51) e alguma variávelBobsTruck
pode ser usado para obter uma referência ao objeto nº 24601 e apontar para um objeto que indica qual carga está contida no caminhão de Bob naquele momento (12h51). Às 12h52, alguns caminhões (incluindo os de Bob) são carregados e descarregados, e as estruturas de dados são atualizadas, de modo queAllTrucks
agora haverá uma referência a uma estrutura de dados que indica que a carga está em todos os caminhões a partir das 12h52.O que deveria acontecer
BobsTruck
?Se a propriedade 'carga' de cada objeto de caminhão for imutável, o objeto 24601 representará para sempre o estado que o caminhão de Bob tinha às 12h51. Se
BobsTruck
tiver uma referência direta ao objeto # 24601, a menos que o código que atualizaAllTrucks
também seja atualizadoBobsTruck
, ele deixará de representar o estado atual do caminhão de Bob. Observe ainda que, a menos queBobsTruck
seja armazenado em alguma forma de objeto mutável, a única maneira que o código que atualizaAllTrucks
poderia atualizá-lo seria se o código fosse explicitamente programado para isso.Se alguém quiser usar
BobsTruck
para observar o estado do caminhão de Bob e ainda manter todos os objetos imutáveis, poderiaBobsTruck
ser uma função imutável que, dado o valor queAllTrucks
tem ou teve em um determinado momento, produzirá o estado do caminhão de Bob em esse tempo. Pode-se até ter um par de funções imutáveis - uma das quais seria como acima, e a outra aceitaria uma referência a um estado de frota e um novo estado de caminhão e retornaria uma referência a um novo estado de frota que combinava com o antigo, exceto que o caminhão de Bob teria o novo estado.Infelizmente, ter que usar essa função sempre que alguém quiser acessar o estado do caminhão de Bob pode se tornar um pouco chato e complicado. Uma abordagem alternativa seria dizer que o objeto nº 24601 representará sempre e para sempre (desde que alguém tenha uma referência a ele) representará o estado atual do caminhão de Bob. O código que deseja acessar repetidamente o estado atual do caminhão de Bob não precisaria executar uma função demorada todas as vezes - ele poderia simplesmente fazer uma função de pesquisa uma vez para descobrir que o objeto # 24601 é o caminhão de Bob e, em seguida, simplesmente acesse esse objeto sempre que quiser ver o estado atual do caminhão de Bob.
Observe que a abordagem funcional não apresenta vantagens em um ambiente de thread único ou em um ambiente de múltiplos threads em que os threads geralmente apenas observam dados em vez de alterá-los. Qualquer encadeamento de observador que copia a referência de objeto contida em
AllTrucks
e, em seguida, examina os estados de caminhão representados, assim, verá o estado de todos os caminhões no momento em que ele pegou a referência. Sempre que um encadeamento de observador quiser ver dados mais recentes, ele pode simplesmente pegar novamente a referência. Por outro lado, ter todo o estado da frota representado por um único objeto imutável impediria a possibilidade de dois encadeamentos atualizarem caminhões diferentes simultaneamente, uma vez que cada encadeamento, se deixado por conta própria, produziria um novo objeto de "estado da frota" que incluía o novo estado de seu caminhão e os antigos estados de todos os outros. A correção pode ser garantida se cada thread usarCompareExchange
para atualizarAllTrucks
apenas se não tiver sido alterado e responder a uma falhaCompareExchange
regenerando seu objeto de estado e tentando novamente a operação, mas se mais de um encadeamento tentar uma operação de gravação simultânea, o desempenho geralmente será pior do que se toda a gravação fosse feita em um único encadeamento; quanto mais threads tentarem operações simultâneas, pior será o desempenho.Se objetos de caminhão individuais são mutáveis, mas têm identidades imutáveis , o cenário multithread torna-se mais limpo. Apenas um segmento pode operar de cada vez em um caminhão, mas os segmentos que operam em caminhões diferentes podem fazê-lo sem interferência. Embora existam maneiras de se imitar esse comportamento, mesmo ao usar objetos imutáveis (por exemplo, é possível definir os objetos "AllTrucks") para que a definição do estado do caminhão pertencente a XXX ao SSS exija simplesmente a geração de um objeto com o título "A partir de [Time], o estado do caminhão pertencente a [XXX] agora é [SSS]; o estado de todo o resto é [Valor antigo da AllTrucks] ". A geração de um objeto desse tipo seria rápida o suficiente para que, mesmo na presença de contenção, um
CompareExchange
loop não demoraria muito. Por outro lado, o uso dessa estrutura de dados aumentaria substancialmente a quantidade de tempo necessária para encontrar o caminhão de uma determinada pessoa. O uso de objetos mutáveis com identidades imutáveis evita esse problema.fonte
Não há certo ou errado, apenas depende do que você preferir. Há uma razão pela qual algumas pessoas preferem linguagens que favorecem um paradigma em detrimento de outro e um modelo de dados em detrimento de outro. Depende apenas da sua preferência e do que você deseja alcançar (e poder usar facilmente as duas abordagens sem alienar os fãs obstinados de um lado ou de outro é um santo graal que algumas línguas estão procurando).
Penso que a melhor e mais rápida maneira de responder à sua pergunta é dirigir-se aos prós e contras da imutabilidade versus mutabilidade .
fonte
Verifique esta postagem do blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Resume por que objetos imutáveis são melhores que mutáveis. Aqui está uma pequena lista de argumentos:
As pessoas estão usando objetos mutáveis, pelo que entendi, porque ainda estão misturando OOP com programação procedural imperativa.
fonte
Em Java, objetos imutáveis requerem um construtor que obtenha todas as propriedades do objeto (ou o construtor as cria a partir de outros argumentos ou padrões). Essas propriedades devem ser marcadas como finais .
Existem quatro problemas com isso principalmente relacionado à ligação de dados :
Você pode atenuar os nºs 1 e 2 usando anotações como
@ConstructorProperties
e criando outro objeto construtor mutável (geralmente fluente) para criar o objeto imutável.fonte
Estou surpreso que ninguém tenha mencionado os benefícios da otimização de desempenho. Dependendo do idioma, um compilador pode fazer várias otimizações ao lidar com dados imutáveis, porque sabe que os dados nunca serão alterados. Todo tipo de coisa é ignorada, o que oferece enormes benefícios de desempenho.
Objetos imutáveis também quase eliminam toda a classe de erros de estado.
Não é tão grande quanto deveria ser porque é mais difícil, não se aplica a todas as línguas e a maioria das pessoas aprendeu a codificação imperativa.
Também descobri que a maioria dos programadores é feliz em suas caixas e frequentemente é resistente a novas idéias que eles não entendem completamente. Em geral, as pessoas não gostam de mudanças.
Lembre-se também de que o estado da maioria dos programadores é ruim. A maior parte da programação que é feita na natureza é terrível e é causada por uma falta de compreensão e política.
fonte
Por que as pessoas usam algum recurso poderoso? Por que as pessoas usam metaprogramação, preguiça ou digitação dinâmica? A resposta é conveniência. O estado mutável é tão fácil. É tão fácil atualizar no local e o limiar do tamanho do projeto, onde o estado imutável é mais produtivo para trabalhar do que o estado mutável, é bastante alto, portanto a escolha não o incomodará por um tempo.
fonte
Linguagens de programação são projetadas para execução por computadores. Todos os blocos de construção importantes dos computadores - CPU, RAM, cache, discos - são mutáveis. Quando eles não são (BIOS), são realmente imutáveis e você também não pode criar novos objetos imutáveis.
Portanto, qualquer linguagem de programação construída sobre objetos imutáveis sofre uma lacuna de representação em sua implementação. E para os primeiros idiomas, como o C, esse foi um grande obstáculo.
fonte
Sem objetos mutáveis, você não tem estado. É certo que isso é bom se você puder gerenciá-lo e se houver alguma chance de um objeto ser referenciado em mais de um encadeamento. Mas o programa será bastante chato. Muitos softwares, principalmente servidores da Web, evitam assumir a responsabilidade por objetos mutáveis, desativando a mutabilidade em bancos de dados, sistemas operacionais, bibliotecas de sistemas etc. Por uma questão prática, isso libera o programador de problemas de mutabilidade e torna a Web (e outras) desenvolvimento acessível. Mas a mutabilidade ainda está lá.
Em geral, você tem três tipos de classes: classes normais, sem thread-safe, que devem ser cuidadosamente guardadas e protegidas; classes imutáveis, que podem ser usadas livremente; e classes mutáveis e seguras para encadeamento que podem ser usadas livremente, mas que devem ser escritas com extremo cuidado. O primeiro tipo é o problemático, com os piores sendo aqueles que se pensa serem do terceiro tipo. Obviamente, o primeiro tipo é o mais fácil de escrever.
Normalmente, acabo com muitas aulas normais e mutáveis que tenho que assistir com muito cuidado. Em uma situação de multithread, a sincronização necessária retarda tudo, mesmo quando posso evitar um abraço mortal. Então, geralmente estou fazendo cópias imutáveis da classe mutável e entregando-as a quem puder usá-la. Uma nova cópia imutável é necessária toda vez que o sinal original muda, então imagino que às vezes eu possa ter cem cópias do original por aí. Sou totalmente dependente da coleta de lixo.
Em resumo, objetos mutáveis e não seguros para threads são bons se você não estiver usando vários threads. (Mas o multithreading está inflando em todos os lugares - tenha cuidado!) Eles podem ser usados com segurança, caso contrário, se você os restringir a variáveis locais ou sincronizá-los rigorosamente. Se você puder evitá-los usando o código comprovado de outras pessoas (DBs, chamadas de sistema etc.), faça-o. Se você pode usar uma classe imutável, faça-o. E eu acho que , em geral , as pessoas não têm conhecimento de problemas de multithreading ou (sensatamente) têm medo deles e usam todos os tipos de truques para evitar a multithreading (ou melhor, responsabilizando-o por outro lugar).
Como PS, sinto que os getters e setters Java estão ficando fora de controle. Veja isso .
fonte
Muitas pessoas tiveram boas respostas, por isso quero ressaltar algo que você tocou que foi muito observador e extremamente verdadeiro e não foi mencionado em nenhum outro lugar aqui.
A criação automática de setters e getters é uma ideia horrível e horrível, mas é a primeira maneira pela qual as pessoas com mentalidade processual tentam forçar o OO a entrar em sua mentalidade. Setters e getters, juntamente com propriedades, só devem ser criados quando você achar necessário e não por padrão.
De fato, embora você precise de getters com bastante regularidade, a única maneira que os setters ou propriedades graváveis devem existir no seu código é através de um padrão de construtor no qual eles são bloqueados depois que o objeto é completamente instanciado.
Muitas classes são mutáveis após a criação, o que é bom, apenas não deve ter suas propriedades manipuladas diretamente - em vez disso, deve-se pedir para manipular suas propriedades através de chamadas de método com lógica de negócios real (sim, um setter é praticamente o mesmo como manipular diretamente a propriedade)
Agora, isso também não se aplica aos códigos / linguagens de estilo "Script", mas ao código que você cria para outra pessoa e espera que outras pessoas leiam repetidamente ao longo dos anos. Ultimamente, tive que começar a fazer essa distinção, porque gosto muito de mexer com o Groovy e há uma enorme diferença nos alvos.
fonte
Objetos mutáveis são usados quando você precisa definir múltiplos valores após instanciar o objeto.
Você não deve ter um construtor com, digamos, seis parâmetros. Em vez disso, você modifica o objeto com métodos setter.
Um exemplo disso é um objeto de relatório, com configuradores para fonte, orientação etc.
Resumindo: mutables são úteis quando você tem muito estado para definir em um objeto e não seria prático ter uma assinatura de construtor muito longa.
EDIT: O padrão do construtor pode ser usado para compilar todo o estado do objeto.
fonte
new ReportBuilder().font("Arial").orientation("landscape").build()
withFont
retorne aReport
.Eu acho que o uso de objetos mutáveis deriva do pensamento imperativo: você calcula um resultado alterando o conteúdo de variáveis mutáveis passo a passo (computação por efeito colateral).
Se você pensa funcionalmente, deseja ter um estado imutável e representar estados subsequentes de um sistema aplicando funções e criando novos valores a partir dos antigos.
A abordagem funcional pode ser mais limpa e robusta, mas pode ser muito ineficiente por causa da cópia, para que você deseje voltar a uma estrutura de dados compartilhada que modifique gradualmente.
A compensação que considero mais razoável é: Comece com objetos imutáveis e depois mude para objetos mutáveis se a sua implementação não for rápida o suficiente. Desse ponto de vista, o uso sistemático de objetos mutáveis desde o início pode ser considerado algum tipo de otimização prematura : você escolhe a implementação mais eficiente (mas também mais difícil de entender e depurar) desde o início.
Então, por que muitos programadores usam objetos mutáveis? IMHO por duas razões:
fonte
Eu sei que você está perguntando sobre Java, mas eu uso mutável vs imutável o tempo todo no objetivo-c. Há uma matriz imutável NSArray e uma matriz mutável NSMutableArray. Essas são duas classes diferentes escritas especificamente de maneira otimizada para lidar com o uso exato. Se eu precisar criar uma matriz e nunca alterar seu conteúdo, eu usaria o NSArray, que é um objeto menor e é muito mais rápido no que faz, comparado a uma matriz mutável.
Portanto, se você criar um objeto Person imutável, precisará apenas de um construtor e getters, assim o objeto será menor e usará menos memória, o que, por sua vez, tornará seu programa realmente mais rápido. Se você precisar alterar o objeto após a criação, um objeto Person mutável será melhor para que ele possa alterar os valores em vez de criar um novo objeto.
Portanto: dependendo do que você planeja fazer com o objeto, escolher mutável ou imutável pode fazer uma enorme diferença quando se trata de desempenho.
fonte
Além de muitas outras razões apresentadas aqui, um problema é que os idiomas convencionais não suportam bem a imutabilidade. Pelo menos, você é penalizado pela imutabilidade, pois precisa adicionar palavras-chave adicionais, como const ou final, e usar construtores ilegíveis com muitos, muitos argumentos ou códigos de padrões de construtores longos.
Isso é muito mais fácil em idiomas criados com imutabilidade em mente. Considere este trecho de Scala para definir uma classe Person, opcionalmente com argumentos nomeados, e criar uma cópia com um atributo alterado:
Portanto, se você deseja que os desenvolvedores adotem a imutabilidade, esse é um dos motivos pelos quais você pode considerar mudar para uma linguagem de programação mais moderna.
fonte
Imutável significa que você não pode alterar o valor, e mutável significa que você pode alterar o valor se pensar em termos de primitivos e objetos. Os objetos são diferentes das primitivas em Java, pois são construídos em tipos. As primitivas são construídas em tipos como int, boolean e void.
Muitas pessoas pensam que variáveis primitivas e de objetos que têm um modificador final na frente delas são imutáveis, no entanto, isso não é exatamente verdade. Portanto, final quase não significa imutável para variáveis. Confira este link para um exemplo de código:
fonte
Eu acho que uma boa razão seria modelar "objetos" mutáveis da "vida real", como janelas de interface. Lembro-me vagamente de ter lido que o OOP foi inventado quando alguém tentou escrever um software para controlar alguma operação no porto de carga.
fonte
Java, em muitos casos, exige objetos mutáveis, por exemplo, quando você deseja contar ou retornar algo em um visitante ou executável, você precisa de variáveis finais, mesmo sem multithreading.
fonte
final
é na verdade cerca de 1/4 de um passo em direção à imutabilidade. Não vendo por que você mencionou.final
. Trazê-lo nesse contexto evoca uma mistura realmente estranha definal
"mutável", quando o objetivofinal
é evitar certos tipos de mutação. (BTW, não meu