Objetos mutáveis ​​vs imutáveis

Eu estou tentando obter minha cabeça em torno de objects mutáveis ​​vs imutáveis. Usar objects mutáveis ​​recebe muita pressão negativa (por exemplo, retornar 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 melhores práticas para usar objects mutáveis? Você deve evitá-los sempre que possível?

    Bem, existem alguns aspectos disso. Número um, objects mutáveis ​​sem identidade de referência podem causar erros em momentos ímpares. Por exemplo, considere um bean Person com um método equals baseado em valor:

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

    A instância Person fica “perdida” no mapa quando usada como chave porque é hashCode e a igualdade é baseada em valores mutáveis. Esses valores foram alterados fora do mapa e todo o hashing tornou-se obsoleto. Os teóricos gostam de insistir nesse ponto, mas, na prática, não achei que fosse um problema muito grande.

    Outro aspecto é a “razoabilidade” lógica do seu código. Este é um termo difícil de definir, abrangendo tudo, desde a legibilidade até o stream. Genericamente, você deve poder olhar para um pedaço 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 faz o que faz corretamente . Quando os objects podem mudar de forma independente em diferentes domínios de código, às vezes torna-se difícil manter o controle de onde e por quê (“ação assustadora à distância”). Este é um conceito mais difícil de exemplificar, mas é algo que muitas vezes é enfrentado em arquiteturas maiores e mais complexas.

    Finalmente, objects mutáveis ​​são matadores em situações simultâneas. Sempre que você acessa um object mutável de segmentos separados, você tem que lidar com o bloqueio. Isso reduz o rendimento e torna seu código mais difícil de manter. Um sistema suficientemente complicado derruba este 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, collections imutáveis) evitam todos esses problemas. Depois que você entender como eles funcionam, seu código se tornará algo mais fácil de ler, mais fácil de manter e menos provável de falhar de maneiras estranhas e imprevisíveis. Objetos imutáveis ​​são ainda mais fáceis de serem testados, devido não somente à sua fácil imitação, mas também aos padrões de código que eles tendem a impor. Em suma, eles são boas práticas ao redor!

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

    Objetos imutáveis ​​vs. collections imutáveis

    Um dos pontos mais refinados no debate sobre objects mutáveis ​​versus objects imutáveis ​​é a possibilidade de estender o conceito de imutabilidade às collections. Um object imutável é um object que geralmente representa uma única estrutura lógica de dados (por exemplo, uma cadeia imutável). Quando você tem uma referência a um object imutável, o conteúdo do object 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 no lugar 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 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 unida e as operações push / pop. Você pode reutilizar todos os nós da coleção anterior na nova coleção, incluindo apenas um nó para o push e clonando sem nós para o pop. A operação push_tail em uma lista unida, por outro lado, não é tão simples ou eficiente.

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

    Algumas linguagens funcionais levam o conceito de imutabilidade às próprias referências de object, permitindo apenas uma única atribuição de referência.

    • Em Erlang isto é verdade para todas as “variables”. Eu só posso atribuir objects 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 vars permitindo uma estrutura de programa mais semelhante a c ou java.
    • A declaração var / val é necessária, enquanto muitas linguagens tradicionais usam modificadores opcionais, como final em java e const in c.

    Facilidade de desenvolvimento vs. desempenho

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

    A principal desvantagem é o desempenho. Aqui está um write-up em um teste simples que fiz em Java comparando alguns objects imutáveis ​​contra mutáveis ​​em um problema de brinquedo.

    Os problemas de desempenho são discutidos em muitos aplicativos, mas não em todos, e é por isso que muitos pacotes numéricos grandes, como a class Numpy Array em Python, permitem atualizações In-loco de grandes matrizes. Isso seria importante para áreas de aplicação que fazem uso de grandes operações matriciais e vetoriais. Esses grandes problemas de paralelismo de dados e de grande intensidade computacional atingem uma grande aceleração operando no local.

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

    Você pode usá-los para objects não-polimórficos de baixo nível – como uma class 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 um IFunction representando uma function matemática – que é usado exclusivamente com a semântica de objects.

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

    Desvantagem: quando usado com objects contendo muitos dados, o consumo de memory pode se tornar um problema. Uma solução para isso poderia ser manter as operações em um object simbólico e fazer avaliação preguiçosa. 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. Algo para evitar definitivamente, neste caso, é retornar grandes pedaços de memory de um método. Em combinação com operações simbólicas encadeadas isso pode levar a um consumo massivo de memory e degradação do desempenho.

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

    Confira esta postagem do blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Explica porque objects imutáveis ​​são melhores que mutáveis. Em resumo:

    • objects imutáveis ​​são mais simples de construir, testar e usar
    • Objetos verdadeiramente imutáveis ​​são sempre seguras
    • 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 falta de atomicidade
    • eles são muito mais fáceis de armazenar em cache

    Você deve especificar de que língua você está falando. Para linguagens de baixo nível como C ou C ++, prefiro usar objects mutáveis ​​para economizar espaço e reduzir a rotatividade de memory. Em linguagens de nível superior, os objects imutáveis ​​tornam mais fácil raciocinar sobre o comportamento do código (especialmente o código multiencadeado), porque não há “uma ação assustadora à distância”.

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

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

    Se um tipo de class é mutável, uma variável desse tipo de class pode ter vários significados diferentes. Por exemplo, suponha que um object foo tenha um campo int[] arr e mantenha uma referência a um int[3] contendo os números {5, 7, 9}. Mesmo que o tipo do campo seja conhecido, há pelo menos quatro coisas diferentes que ele pode representar:

    • Uma referência potencialmente compartilhada, em que todos os proprietários se preocupam apenas com o fato de encapsular os valores 5, 7 e 9. Se foo quiser que os valores diferentes sejam encapsulados, ele deverá substituí-lo por um array diferente que contenha os valores desejados. Se alguém quiser fazer uma cópia de foo , pode-se dar à cópia uma referência a arr ou uma nova matriz contendo os valores {1,2,3}, o que for mais conveniente.

    • A única referência, em qualquer parte do universo, a uma matriz que encapsula os valores 5, 7 e 9. conjunto de três locais de armazenamento que no momento mantêm os valores 5, 7 e 9; Se foo quiser encapsular os valores 5, 8 e 9, pode alterar o segundo item desse array ou criar um novo array contendo os valores 5, 8 e 9 e abandonar o antigo. Note que se alguém quiser fazer uma cópia de foo , deve-se replace a cópia por uma referência a uma nova matriz para que foo.arr permaneça como a única referência a essa matriz em qualquer parte do universo.

    • Uma referência a um array que pertence a algum outro object que o expôs a foo por algum motivo (por exemplo, talvez ele queira armazenar alguns dados lá). Nesse cenário, arr não encapsula o conteúdo da matriz, mas sim sua identidade . Como replace o arr por uma referência a um novo array mudaria totalmente seu significado, uma cópia do foo deveria conter uma referência ao mesmo array.

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

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

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

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

    Objetos são diferentes de primitivos em Java. Primitivos são construídos em tipos (boolean, int, etc) e objects (classs) são tipos criados pelo usuário.

    Primitivos e objects podem ser mutáveis ​​ou imutáveis ​​quando definidos como variables ​​de membro dentro da implementação de uma class.

    Muitas pessoas acham que primitivos e variables ​​de objects com um modificador final na frente deles são imutáveis, no entanto, isso não é exatamente verdade. Tão final quase não significa imutável para variables. Veja o exemplo aqui
    http://www.siteconsortium.com/h/D0000F.php .

    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ê pergunta ao txtfile , posso devolvê-lo em dois modos:

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

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

    No segundo modo, o arquivo txt retornado é um arquivo imutável, porque todas as mudanças no arquivo recebido não se referem ao arquivo original. A vantagem deste modo é que somente eu (proprietário) pode modificar o arquivo original e a desvantagem é que cada cópia retornada exigia memory (na RAM ou no HDD).