Objetos mutáveis ​​vs imutáveis

173

Estou tentando entender os objetos mutáveis ​​e imutáveis. O uso de objetos mutáveis ​​causa muita má impressão (por exemplo, retornando uma matriz de seqüências de caracteres 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?

Alex Angas
fonte
stringé imutável, pelo menos no .NET, e acho que em muitas outras linguagens modernas também.
Domenic
4
depende do que realmente é uma String na linguagem - erlang 'strings' são apenas uma matriz de entradas, e haskell "strings" são matrizes de caracteres.
Chii
4
Strings Ruby são uma matriz mutável de bytes. Absolutamente me deixa louco que você pode alterá-los no lugar.
Daniel Spiewak 18/10/08
6
Daniel - por que? Você se encontra fazendo isso por acidente?
5
@DanielSpiewak A solução para porcas acionadas que você pode alterar as cordas no lugar é simples: simplesmente não faça isso. A solução para acionar as porcas porque você não pode mudar as cordas no lugar não é tão simples.
Kaz

Respostas:

162

Bem, existem alguns aspectos nisso.

  1. Objetos mutáveis ​​sem identidade de referência podem causar erros em momentos estranhos. Por exemplo, considere um Personbean com um equalsmétodo baseado em valor :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    A Personinstância é "perdida" no mapa quando usada como chave, porque sua hashCodeigualdade e foram baseadas em valores mutáveis. Esses valores foram alterados fora do mapa e todo o hash se tornou obsoleto. Os teóricos gostam de falar sobre esse ponto, mas, na prática, não achei que fosse um problema demais.

  2. Outro aspecto é a "razoabilidade" lógica do seu código. Este é um termo difícil de definir, abrangendo tudo, desde a legibilidade ao fluxo. Genericamente, você deve poder analisar um pedaço de código e entender facilmente o que ele faz. Mais importante do que isso, você deve conseguir se convencer de que faz o que faz corretamente . Quando os objetos podem mudar independentemente entre diferentes "domínios" de código, às vezes fica difícil acompanhar o que é onde e por quê (" ação assustadora à distância "). Esse é um conceito mais difícil de exemplificar, mas geralmente é enfrentado em arquiteturas maiores e mais complexas.

  3. Finalmente, objetos mutáveis ​​são matadores em situações simultâneas. Sempre que você acessar um objeto mutável a partir de threads separados, precisará lidar com o bloqueio. Isso reduz a taxa de transferência e torna seu código muito mais difícil de manter. Um sistema suficientemente complicado explode esse problema tão fora de proporção que se torna quase impossível de manter (mesmo para especialistas em concorrência).

Objetos imutáveis ​​(e mais particularmente coleções imutáveis) evitam todos esses problemas. Depois de entender como eles funcionam, seu código se transformará 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, devido não apenas à sua facilidade de zombaria, mas também aos padrões de código que eles tendem a impor. Em suma, são boas práticas ao redor!

Com isso dito, dificilmente sou fanático por esse assunto. Alguns problemas simplesmente não são bons quando tudo é imutável. Mas eu acho que você deve tentar empurrar o máximo de seu código nessa direção possível, assumindo, é claro, que você esteja usando uma linguagem que faça disso uma opinião sustentável (C / C ++ torna isso muito difícil, assim como Java) . Em resumo: as vantagens dependem um pouco do seu problema, mas eu preferiria a imutabilidade.

Daniel Spiewak
fonte
11
Ótima resposta. Uma pequena pergunta, no entanto: o C ++ não oferece um bom suporte à imutabilidade? O recurso const-correctness não é suficiente?
Dimitri C.
1
@DimitriC .: O C ++ realmente possui alguns recursos mais fundamentais, principalmente uma distinção melhor entre locais de armazenamento que deveriam encapsular o estado não compartilhado daqueles que encapsulam a identidade do objeto.
Supercat #
2
Em caso de programação Java Joshua Bloch traz boa explicação sobre este tema em seu famoso livro Effective Java (ponto 15)
dMathieuD
27

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

Um dos pontos mais delicados do debate sobre objetos mutáveis ​​e 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 sequência imutável). Quando você tem uma referência a um objeto imutável, o conteúdo do objeto não muda.

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 no local, e todas as entidades que têm 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 que reflete a alteração. Todas as entidades que têm 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 única e as operações push / pop. Você pode reutilizar todos os nós da coleção anterior na nova coleção, adicionando apenas um nó para o envio e clonando nenhum nó para o pop. A operação push_tail em uma lista isolada, por outro lado, não é tão simples ou eficiente.

Variáveis ​​/ referências imutáveis ​​vs. mutáveis

Algumas linguagens funcionais adotam o conceito de imutabilidade para fazer referência a 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 seria possível reatribuir a nova coleção à referência antiga (nome da variável).
  • O Scala também constrói isso na linguagem, com todas as referências sendo declaradas com var ou val , vals sendo apenas atribuição única e promovendo um estilo funcional, mas vários permitem uma estrutura de programa mais semelhante a C ou Java.
  • A declaração var / val é necessária, enquanto muitos idiomas tradicionais usam modificadores opcionais, como final em java e const em C.

Facilidade de Desenvolvimento vs. Desempenho

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

A principal desvantagem é o desempenho. Aqui está uma descrição de um teste simples que fiz em Java, comparando alguns objetos imutáveis ​​versus 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 no Python, permitem atualizações no local de matrizes grandes. Isso seria importante para áreas de aplicação que fazem uso de grandes operações matriciais e vetoriais. Esses grandes problemas paralelos a dados e intensivos em computação alcançam uma grande velocidade ao operar no local.

Ben Jackson
fonte
12

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. Em resumo:

  • objetos imutáveis ​​são mais simples de construir, testar e usar
  • objetos verdadeiramente imutáveis ​​são sempre seguros para threads
  • 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
yegor256
fonte
10

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

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

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

Maior vantagem: imutabilidade + semântica de objetos + 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 que contêm muitos dados, o consumo de memória pode se tornar um problema. Uma solução para isso pode ser manter as operações em um objeto simbólico e fazer uma avaliação lenta. No entanto, isso 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. Definitivamente, algo a ser evitado nesse caso está retornando grandes pedaços de memória de um método. Em combinação com operações simbólicas encadeadas, isso pode levar a um enorme consumo de memória e degradação do desempenho.

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

QBziZ
fonte
Acho que não entendi a Seção 4 para a maior vantagem: imutabilidade + semântica de objetos + ponteiros inteligentes tornam a propriedade do objeto um ponto "discutível". Então é discutível? Eu acho que você está usando "discutível" incorretamente ... Como a próxima frase é o objeto implica em "comportamento determinístico" de seu comportamento "discutível".
Benjamin
Você está certo, eu usei 'discutível' incorretamente. Considere que mudou :) #
1055 QBziZ
6

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

John Millikin
fonte
Você está sugerindo que os tópicos são quantumly emaranhados? Isso é bastante exagerado :) Os tópicos são realmente, se você pensar sobre isso, bem perto de serem enredados. Uma alteração feita por um encadeamento afeta o outro. 1
ndrewxie
4

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 (consulte a página da Wikipedia sobre o assunto). Um exemplo disso em uma linguagem de programação são as listas e as tuplas do Pythons. As listas podem ser modificadas (por exemplo, novos itens podem ser adicionados após a criação), enquanto as tuplas não podem.

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

willurd
fonte
1

Se um tipo de classe é mutável, uma variável desse tipo de classe pode ter vários significados diferentes. Por exemplo, suponha que um objeto footenha um campo int[] arre mantenha uma referência a uma int[3]retenção dos números {5, 7, 9}. Embora o tipo do campo seja conhecido, há pelo menos quatro coisas diferentes que ele pode representar:

  • Uma referência potencialmente compartilhada, cujos detentores se importam apenas em encapsular os valores 5, 7 e 9. Se foodesejar arrencapsular valores diferentes, deve substituí-lo por uma matriz diferente que contenha os valores desejados. Se alguém quiser fazer uma cópia de foo, poderá dar à cópia uma referência arrou uma nova matriz contendo os valores {1,2,3}, o que for mais conveniente.

  • A única referência, em qualquer lugar do universo, a uma matriz que encapsula os valores 5, 7 e 9. conjunto de três locais de armazenamento que atualmente mantêm os valores 5, 7 e 9; se foodesejar encapsular os valores 5, 8 e 9, ele pode alterar o segundo item nessa matriz ou criar uma nova matriz contendo os valores 5, 8 e 9 e abandonar a antiga. Observe que, se alguém quiser fazer uma cópia de foo, deve-se na cópia substituir arrpor uma referência a uma nova matriz foo.arrpara permanecer como a única referência a essa matriz em qualquer lugar do universo.

  • Uma referência a uma matriz que pertence a algum outro objeto que a tenha exposto foopor algum motivo (por exemplo, talvez ele queira fooarmazenar alguns dados lá). Nesse cenário, arrnão encapsula o conteúdo da matriz, mas sua identidade . Como a substituição arrpor uma referência a uma nova matriz mudaria totalmente seu significado, uma cópia de foodeveria conter uma referência à mesma matriz.

  • Uma referência a uma matriz da qual fooé o único proprietário, mas à qual as referências são mantidas por outro objeto por algum motivo (por exemplo, ele deseja ter o outro objeto para armazenar dados lá - o outro lado do caso anterior). Nesse cenário, arrencapsula a identidade da matriz e seu conteúdo. Substituir arrpor uma referência a uma nova matriz mudaria totalmente seu significado, mas a arrreferência de um clone foo.arrviolaria a suposição de que fooé o único proprietário. Portanto, não há como copiar foo.

Em teoria, int[]deve ser um tipo bem 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. Grande parte do "poder" de objetos imutáveis ​​decorre desse fato.

supercat
fonte
1

Instâncias mutáveis são passadas por referência.

Instâncias imutáveis são passadas por valor.

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

  1. Crie um atalho para txtfile e atalho para você ou
  2. Faça uma cópia para txtfile e pas copy para você.

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

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

Teodor
fonte
Isto não é estritamente verdade. Instâncias imutáveis ​​podem realmente ser passadas por referência, e geralmente são. A cópia é feita principalmente quando você precisa atualizar o objeto ou a estrutura de dados.
21716 Jamon Holmgren
0

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

user1923551
fonte
0

Meios imutáveis ​​não podem ser alterados e mutáveis ​​significa que você pode mudar.

Os objetos são diferentes das primitivas em Java. As primitivas são construídas em tipos (booleano, 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. Portanto, final quase não significa imutável para variáveis. Veja o exemplo aqui
http://www.siteconsortium.com/h/D0000F.php .

JTHouseCat
fonte
0

Immutable object- é um estado do objeto que não pode ser alterado após a criação. O objeto é imutável quando todos os campos são imutáveis

Discussão segura

A principal vantagem do objeto Imutável é que ele é um ambiente natural para simultâneo. O maior problema na simultaneidade é o shared resourceque pode ser alterado em qualquer segmento. Porém, se um objeto é imutável, é read-onlyessa a operação segura do thread. Qualquer modificação de um objeto imutável original retorna uma cópia

efeitos colaterais grátis

Como desenvolvedor, você tem certeza absoluta de que o estado do objeto imutável não pode ser alterado de nenhum lugar (de propósito ou não)

otimização de compilação

Melhorar o desempenho

Desvantagem:

A cópia do objeto é uma operação mais pesada do que a alteração de um objeto mutável, é por isso que ele tem alguma pegada de desempenho

Para criar um immutableobjeto, você deve usar:

  1. Nível de idioma. Cada idioma contém ferramentas para ajudá-lo. Por exemplo, Java possui finale primitives, Swift possui lete struct[Sobre] . Idioma define um tipo de variável. Por exemplo, Java possui primitivee referencedigite, Swift possui valuee referencedigite [Sobre] . Para objetos imutáveis, é mais conveniente o tipo primitivese o valuetipo que faz uma cópia por padrão. Quanto ao referencetipo, é mais difícil (porque você pode alterar o estado do objeto), mas é possível. Por exemplo, você pode usar clonepadrão no nível do desenvolvedor

  2. Nível de desenvolvedor. Como desenvolvedor, você não deve fornecer uma interface para alterar o estado

yoAlex5
fonte