Pergunta

Estou tentando entender objetos mutáveis ​​​​e imutáveis.O uso de objetos mutáveis ​​recebe muita publicidade negativa (por exemploretornando uma matriz de strings de um método), mas estou tendo problemas para entender quais são os impactos negativos disso.Quais são as práticas recomendadas para o uso de objetos mutáveis?Você deve evitá-los sempre que possível?

Foi útil?

Solução

Bem, há alguns aspectos nisso.Número um, objetos mutáveis ​​sem identidade de referência podem causar bugs em momentos estranhos.Por exemplo, considere um Person bean com base em valor equals método:

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

O Person instância fica "perdida" no mapa quando usada como chave porque é hashCode e a igualdade baseavam-se em valores mutáveis.Esses valores mudaram fora do mapa e todo o hashing tornou-se obsoleto.Os teóricos gostam de insistir neste ponto, mas na prática não considero que isso seja um grande problema.

Outro aspecto é a "razoabilidade" lógica do seu código.Este é um termo difícil de definir, abrangendo tudo, desde a legibilidade até o fluxo.Genericamente, você deve ser capaz de observar um trecho de código e entender facilmente o que ele faz.Mas mais importante do que isso, você deve ser capaz de se convencer de que ele faz o que faz corretamente.Quando os objetos podem mudar de forma independente em diferentes "domínios" de código, às vezes fica difícil acompanhar o que está onde e por quê ("ação assustadora à distância").Este é um conceito mais difícil de exemplificar, mas é algo frequentemente enfrentado em arquiteturas maiores e mais complexas.

Finalmente, objetos mutáveis ​​são assassino em situações simultâneas.Sempre que você acessa um objeto mutável a partir de threads separados, você precisa lidar com o bloqueio.Isso reduz o rendimento e torna seu código dramaticamente mais difícil de manter.Um sistema suficientemente complicado torna esse problema tão desproporcional que se torna quase impossível mantê-lo (mesmo para especialistas em simultaneidade).

Objetos imutáveis ​​(e mais particularmente coleções imutáveis) evitam todos esses problemas.Depois de entender como eles funcionam, seu código se desenvolverá em algo mais fácil de ler, mais fácil de manter e com menor probabilidade de falhar de maneiras estranhas e imprevisíveis.Objetos imutáveis ​​são ainda mais fáceis de testar, não apenas devido à sua fácil zombaria, mas também aos padrões de código que eles tendem a impor.Resumindo, eles são uma boa prática para todos!

Dito isto, dificilmente sou um fanático neste assunto.Alguns problemas simplesmente não funcionam bem quando tudo é imutável.Mas eu acho que você deve tentar empurrar o máximo possível do seu código nessa direção, supondo, é claro, que você esteja usando uma linguagem que torne essa opinião sustentável (C/C++ torna isso muito difícil, assim como Java) .Resumidamente:as vantagens dependem um pouco do seu problema, mas eu tenderia a preferir a imutabilidade.

Outras dicas

Objetos imutáveis ​​vs.Coleções imutáveis

Um dos pontos mais delicados do debate sobre mutável vs.objetos imutáveis ​​é a possibilidade de estender o conceito de imutabilidade às coleções.Um objeto imutável é um objeto que geralmente representa uma única estrutura lógica de dados (por exemplo, uma string imutável).Quando você tem uma referência a um objeto imutável, o conteúdo do objeto não será alterado.

Uma coleção imutável é uma coleção que nunca muda.

Quando executo uma operação em uma coleção mutável, altero a coleção e todas as entidades que possuem referências à coleção verão a alteração.

Quando executo uma operação em uma coleção imutável, uma referência é retornada para uma nova coleção refletindo a alteração.Todas as entidades que possuem referências a versões anteriores da coleção não verão a alteração.

Implementações inteligentes não precisam necessariamente copiar (clonar) toda a coleção para fornecer essa imutabilidade.O exemplo mais simples é a pilha implementada como uma lista vinculada individualmente e as operações push/pop.Você pode reutilizar todos os nós da coleção anterior na nova coleção, adicionando apenas um único nó para o push e não clonando nenhum nó para o pop.A operação push_tail em uma lista vinculada individualmente, por outro lado, não é tão simples ou eficiente.

Imutável vs.Variáveis/referências mutáveis

Algumas linguagens funcionais adotam o conceito de imutabilidade para as próprias referências de objetos, permitindo apenas uma única atribuição de referência.

  • Em Erlang isso é verdade para todas as "variáveis".Só posso atribuir objetos a uma referência uma vez.Se eu operasse em uma coleção, não conseguiria reatribuir a nova coleção à referência antiga (nome da variável).
  • Scala também incorpora isso na linguagem com todas as referências sendo declaradas com var ou valor, vals sendo apenas uma atribuição única e promovendo um estilo funcional, mas vars permitindo uma estrutura de programa mais semelhante a c ou java.
  • A declaração var/val é obrigatória, enquanto muitas linguagens tradicionais usam modificadores opcionais, como final em java e const em c.

Facilidade de Desenvolvimento vs.Desempenho

Quase sempre a razão para usar um objeto imutável é promover programação livre de efeitos colaterais e raciocínio simples sobre o código (especialmente em um ambiente altamente simultâneo/paralelo).Você não precisa se preocupar com a alteração dos dados subjacentes por outra entidade se o objeto for imutável.

A principal desvantagem é o desempenho.Aqui está um artigo sobre um teste simples que fiz em Java comparando alguns imutáveis ​​vs.objetos mutáveis ​​em um problema de brinquedo.

Os problemas de desempenho são discutíveis em muitos aplicativos, mas não em todos, e é por isso que muitos pacotes numéricos grandes, como a classe Numpy Array em Python, permitem atualizações no local de grandes arrays.Isso seria importante para áreas de aplicação que fazem uso de grandes operações matriciais e vetoriais.Esses grandes problemas paralelos de dados e computacionalmente intensivos alcançam uma grande velocidade ao operar no local.

Verifique esta postagem do blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html.Explica por que objetos imutáveis ​​são melhores que mutáveis.Resumidamente:

  • objetos imutáveis ​​são mais simples de construir, testar e usar
  • objetos verdadeiramente imutáveis ​​são sempre thread-safe
  • eles ajudam a evitar o acoplamento temporal
  • seu uso é livre de efeitos colaterais (sem cópias defensivas)
  • problema de mutabilidade de identidade é evitado
  • eles sempre têm atomicidade de falha
  • eles são muito mais fáceis de armazenar em cache

Objetos imutáveis ​​são um conceito muito poderoso.Eles eliminam grande parte do fardo de tentar manter objetos/variáveis ​​consistentes para todos os clientes.

Você pode usá-los para objetos não polimórficos de baixo nível - como uma classe CPoint - que são usados ​​principalmente com semântica de valor.

Ou você pode usá-los para interfaces polimórficas de alto nível - como uma IFunction representando uma função matemática - que é usada exclusivamente com semântica de objetos.

Maior vantagem:imutabilidade + semântica do objeto + ponteiros inteligentes tornam a propriedade do objeto um problema, todos os clientes do objeto têm sua própria cópia privada por padrão.Implicitamente, isso também significa comportamento determinístico na presença de simultaneidade.

Desvantagem:quando usado com objetos contendo muitos dados, o consumo de memória pode se tornar um problema.Uma solução para isso poderia ser manter as operações em um objeto simbólicas e fazer uma avaliação preguiçosa.No entanto, isto pode levar a cadeias de cálculos simbólicos, que podem influenciar negativamente o desempenho, se a interface não for projetada para acomodar operações simbólicas.Algo a evitar definitivamente neste caso é retornar grandes pedaços de memória de um método.Em combinação com operações simbólicas encadeadas, isso pode levar a um consumo massivo de memória e à degradação do desempenho.

Portanto, objetos imutáveis ​​são definitivamente minha principal maneira de pensar sobre design orientado a objetos, mas não são um dogma.Eles resolvem muitos problemas para clientes de objetos, mas também criam muitos, principalmente para os implementadores.

Você deve especificar de que idioma está falando.Para linguagens de baixo nível como C ou C++, prefiro usar objetos mutáveis ​​para conservar espaço e reduzir a perda de memória.Em linguagens de nível superior, objetos imutáveis ​​facilitam o raciocínio sobre o comportamento do código (especialmente código multithread) porque não há "ação assustadora à distância".

Um objeto mutável é simplesmente um objeto que pode ser modificado após ser criado/instanciado, versus um objeto imutável que não pode ser modificado (veja a página da Wikipédia sobre o assunto).Um exemplo disso em uma linguagem de programação são as listas e tuplas do Python.As listas podem ser modificadas (por exemplo, novos itens podem ser adicionados após serem criados), enquanto as tuplas não.

Eu realmente não acho que haja uma resposta clara sobre qual é o melhor para todas as situações.Ambos têm seus lugares.

Se um tipo de classe for mutável, uma variável desse tipo de classe pode ter vários significados diferentes.Por exemplo, suponha que um objeto foo tem um campo int[] arr, e contém uma referência a um int[3] segurando os números {5, 7, 9}.Embora o tipo de campo seja conhecido, há pelo menos quatro coisas diferentes que ele pode representar:

  • Uma referência potencialmente compartilhada, cujos detentores se importam apenas que ela encapsule os valores 5, 7 e 9.Se foo quer arr para encapsular valores diferentes, ele deve substituí-lo por um array diferente que contenha os valores desejados.Se alguém quiser fazer uma cópia foo, pode-se dar à cópia uma referência a arr ou um novo array contendo os valores {1,2,3}, o que for mais conveniente.

  • A única referência, em qualquer lugar do universo, a um array que encapsula os valores 5, 7 e 9.conjunto de três locais de armazenamento que atualmente possuem os valores 5, 7 e 9;se foo deseja encapsular os valores 5, 8 e 9, ele pode alterar o segundo item desse array ou criar um novo array contendo os valores 5, 8 e 9 e abandonar o antigo.Observe que se alguém quiser fazer uma cópia do foo, deve-se substituir na cópia arr com uma referência a um novo array para que foo.arr permanecer como a única referência a essa matriz em qualquer lugar do universo.

  • Uma referência a um array que pertence a algum outro objeto que o expôs foo por algum motivo (por ex.talvez queira foo para armazenar alguns dados lá).Neste cenário, arr não encapsula o conteúdo do array, mas sim seu identidade.Porque substituir arr com uma referência a um novo array mudaria totalmente seu significado, uma cópia de foo deve conter uma referência ao mesmo array.

  • Uma referência a uma matriz da qual foo é o único proprietário, mas cujas referências são mantidas por outro objeto por algum motivo (por exemplo,ele quer ter o outro objeto para armazenar dados lá - o outro lado do caso anterior).Neste cenário, arr encapsula a identidade da matriz e seu conteúdo.Substituindo arr com uma referência a um novo array mudaria totalmente seu significado, mas ter um clone arr referir-se foo.arr violaria a suposição de que foo é o único proprietário.Portanto, não há como copiar foo.

Em teoria, int[] deve ser um tipo simples e bem definido, mas tem quatro significados muito diferentes.Por outro lado, uma referência a um objeto imutável (por exemplo String) geralmente tem apenas um significado.Muito do “poder” dos objetos imutáveis ​​decorre desse fato.

Se você retornar referências de uma matriz ou string, o mundo externo poderá modificar o conteúdo desse objeto e, portanto, torná-lo um objeto mutável (modificável).

Imutável significa que não pode ser alterado e mutável significa que você pode mudar.

Os objetos são diferentes dos primitivos em Java.Primitivos são tipos incorporados (boolean, int, etc) e objetos (classes) são tipos criados pelo usuário.

Primitivos e objetos podem ser mutáveis ​​ou imutáveis ​​quando definidos como variáveis ​​de membro na implementação de uma classe.

Muitas pessoas pensam que primitivas e variáveis ​​de objeto com um modificador final na frente delas são imutáveis, no entanto, isso não é exatamente verdade.Então final quase não significa imutável para variáveis.Veja exemplo aqui
http://www.siteconsortium.com/h/D0000F.php.

Mutável instâncias é passada por referência.

Imutável instâncias é passada por valor.

Exemplo abstrato.Suponha que exista um arquivo chamado arquivo txt no meu disco rígido.Agora, quando você pergunta arquivo txt de mim, posso devolvê-lo de dois modos:

  1. Crie um atalho para arquivo txt e pas atalho para você, ou
  2. Leve uma cópia para arquivo txt e copie para você.

No primeiro modo, retornou arquivo txt é um arquivo mutável, porque quando você faz alterações no arquivo de atalho, você também faz alterações no arquivo original.A vantagem deste modo é que cada atalho retornado requer menos memória (na RAM ou no disco rígido) e a desvantagem é que todos (não só eu, proprietário) têm permissões para modificar o conteúdo do arquivo.

No segundo modo, retornou arquivo txt é um arquivo imutável, pois todas as alterações no arquivo recebido não se referem ao arquivo original.A vantagem deste modo é que somente eu (proprietário) posso modificar o arquivo original e a desvantagem é que cada cópia retornada requer memória (na RAM ou no HDD).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top