Se objetos imutáveis ​​são bons, por que as pessoas continuam criando objetos mutáveis? [fechadas]


250

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.


42
Eu acho que, além de razões legítimas (como mencionado por Péter abaixo), "desenvolvedor preguiçoso" é um motivo mais comum que desenvolvedor "estúpido" .E antes de "desenvolvedor estúpido", há também "desenvolvedor desinformado".
Joachim Sauer

201
Para todo programador / blogueiro evangélico, existem 1000 leitores ávidos de blogs que se reinventam imediatamente e adotam as técnicas mais recentes. Para cada um desses, existem 10.000 programadores lá fora, com o nariz voltado para a pedra de amolar, trabalhando um dia e obtendo o produto pela porta. Esses caras estão usando técnicas testadas e confiáveis ​​que funcionam para eles há anos. Eles esperam até que novas técnicas sejam amplamente adotadas e mostrem benefícios reais antes de adotá-las. Não os chame de estúpidos, e eles são tudo menos preguiçosos, chame-os de "ocupados".
Preocupação binária

22
@BinaryWorrier: objetos imutáveis ​​dificilmente são uma "coisa nova". Eles podem não ter sido usados ​​muito para objetos de domínio, mas Java e C # os possuíam desde o início. Além disso: "preguiçoso" nem sempre é uma palavra ruim, alguns tipos de "preguiçoso" são uma vantagem absoluta para um desenvolvedor.
Joachim Sauer

11
@Joachim: Eu acho que é bastante óbvio que "preguiçoso" foi usado no sentido pejorativo acima :) Além disso, objetos imutáveis ​​(como Lambda Calculus e OOP na época - sim, eu sou tão velho) não precisam ser novos de repente se tornar o sabor do mês . Eu não estou argumentando que eles são uma coisa ruim (eles não são), ou que eles não têm o seu lugar (eles obviamente têm), são fáceis para as pessoas porque eles não ouviram as últimas Boas Palavras e foi convertido como fervorosamente (não culpando você pelo comentário "preguiçoso", sei que você tentou atenuá-lo).
Preocupação binária

116
-1, objetos imutáveis ​​não são 'bons'. Apenas mais ou menos apropriado para situações específicas. Qualquer pessoa que diga a você uma técnica ou outra é objetivamente 'boa' ou 'ruim' em relação a outra em todas as situações está vendendo sua religião.
GrandmasterB

Respostas:


326

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!"


27
Está certo. Especialmente na programação da GUI, objetos mutáveis ​​são muito úteis.
Florian Salihovic

23
Não é apenas resistência, tenho certeza de que muitos desenvolvedores adorariam experimentar o melhor e o mais recente, mas com que frequência novos projetos aparecem no ambiente médio do desenvolvedor, onde eles podem aplicar essas novas práticas? Nem todo mundo pode ou irá escrever um projeto de hobby apenas para experimentar um estado imutável.
Steven Evers

29
Duas pequenas advertências para isso: (1) Pegue o personagem do jogo em movimento. A Pointclasse 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.
Konrad Rudolph

4
@KonradRudolph, bons pontos, obrigado. Não pretendia excluir o uso da imutabilidade em objetos complexos, mas implementar essa classe de maneira correta e eficiente está longe de ser uma tarefa trivial, e o esforço extra necessário nem sempre pode ser justificado.
Péter Török

6
Você faz um bom argumento sobre estado versus identidade. É por isso que Rich Hickey (autor de Clojure) separou os dois em Clojure. Pode-se argumentar que o carro que você possui com 1/2 tanque de gasolina não é o mesmo que aquele com 1/4 de tanque de gasolina. Eles têm a mesma identidade, mas não são a mesma coisa, todo "tique" do tempo da nossa realidade cria um clone de todos os objetos em nosso mundo, nossos cérebros simplesmente os juntam com uma identidade comum. Clojure possui refs, átomos, agentes etc. para representar o tempo. E mapas, vetores e listas para o tempo real.
Timothy Baldridge

130

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.


9
Na maioria dos IDEs modernos que conheço, é preciso praticamente o mesmo esforço para gerar apenas getters, assim como para gerar getters e setters. Embora seja verdade que a adição final, constetc. leva um pouco de esforço extra ... a menos que você configurar um modelo de código :-)
Péter Török

10
@ PéterTörök não é apenas o esforço adicional - é o fato de que seus colegas programadores vão querer enforcá-lo com eficiência, porque acham seu estilo de codificação tão estranho à experiência deles. Isso também desencoraja esse tipo de coisa.
Onorio Catenacci

5
Isso pode ser superado com mais comunicação e educação, por exemplo, é aconselhável conversar ou fazer uma apresentação para os colegas de equipe sobre um novo estilo de codificação antes de introduzi-lo em uma base de código existente. É verdade que um bom projeto possui um estilo de codificação comum, que não é apenas uma amálgama dos estilos de codificação preferidos por todos os membros do projeto (passado e presente). Portanto, a introdução ou alteração de idiomas de codificação deve ser uma decisão da equipe.
Péter Török

3
@ PéterTörök Em uma linguagem imperativa, objetos mutáveis ​​são o comportamento padrão. Se você deseja apenas objetos imutáveis, é melhor mudar para uma linguagem funcional.
Emory

3
@ PéterTörök É um pouco ingênuo supor que incorporar imutabilidade em um programa envolve nada mais do que eliminar todos os setters. Você ainda precisa de uma maneira para o estado do programa mudar de forma incremental e, para isso, precisa de construtores, imutabilidade de picolé ou proxies mutáveis, os quais levam cerca de 1 bilhão de vezes o esforço que apenas os levantadores precisavam.
Asad Saeeduddin

49
  1. 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

  2. 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).

  3. 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?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF., Se você tem uma opinião diferente sobre como manter gráficos complexos com relacionamentos bidirecionais usando apenas objetos imutáveis, adoraria ouvi-lo. (Presumo que podemos concordar que uma coleção / matriz é um objeto)
smartcaveman

2
@AndresF., (1) Minha primeira afirmação não foi universal, portanto não foi falsa. Na verdade, forneci um exemplo de código para explicar como isso era necessariamente verdade em certos casos que são muito comuns no desenvolvimento de aplicativos. (2) Relações bidirecionais exigem mutabilidade, em geral. Não acredito que Java seja o culpado. Como eu disse, ficaria feliz em avaliar as alternativas construtivas que você sugeriria, mas, nesse momento, seus comentários parecem muito com "Você está errado porque eu disse isso".
218126 phpBaixar

14
@smartcaveman Sobre (2), também discordo: em geral, uma "relação bidirecional" é um conceito matemático ortogonal à mutabilidade. Como geralmente implementado em Java, requer mutabilidade (eu concordei com você nesse ponto). No entanto, posso pensar em uma implementação alternativa: uma classe Relationship entre os dois objetos, com um construtor Relationship(a, b); no momento de criar o relacionamento, ambas as entidades ae bjá 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.
Andres F.

4
@AndresF., Então, com base no que você está dizendo, Se Ré o Relationship(a,b)e os dois ae bsão imutáveis, nem atampouco bmanterá uma referência R. 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?
238126 phpBaixar

9
É possível armazenar um relacionamento bidirecional para dados imutáveis, como Chthulhu aponta por preguiça. Aqui está uma maneira de fazer isso: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

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.


3
Este é o livro da Okasaki?
Caracol mecânico

Sim. meio seco, mas uma tonelada de boas informações ...
Paul Sanwald

4
Engraçado, eu sempre pensei que a árvore vermelha / preta de Okasakis era muito mais simples. 10 linhas ou mais. Eu acho que a maior vantagem é quando você realmente deseja manter a versão antiga também.
Thomas Ahle

Embora a última frase provavelmente tenha sido verdadeira no passado, não está claro se ela permanecerá verdadeira no futuro, dadas as tendências atuais de hardware etc.
jk.

29

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.


Talvez "conhecido" e não "popular" seja uma melhor escolha de palavra.
Den

Sim está certo.
Florian Salihovic

5
+1: eu concordo: depois de aprender alguns Scala e Haskell, costumo usar final em Java e const em C ++ em todos os lugares. Também uso objetos imutáveis, se possível e, embora objetos mutáveis ​​ainda sejam necessários com muita frequência, é incrível a frequência com que você pode usar objetos imutáveis.
Giorgio

5
Li esse meu comentário depois de dois anos e meio e minha opinião mudou em favor da imutabilidade. No meu projeto atual (que está em Python), estamos usando objetos mutáveis ​​muito raramente. Até nossos dados persistentes são imutáveis: criamos novos registros como resultado de algumas operações e excluímos registros antigos quando eles não são mais necessários, mas nunca atualizamos nenhum registro no disco. Desnecessário dizer que isso tornou nosso aplicativo simultâneo e multiusuário muito mais fácil de implementar e manter até agora.
Giorgio

13

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.


4
+1, o padrão getter / setter é usado com muita frequência como algum tipo de implementação padrão após a primeira análise de dados.
Jaap

Este é provavelmente um grande ponto ... "porque é assim que todo mundo está fazendo isso" ... então deve estar certo. Para um programa "Hello World", é provavelmente mais fácil. Gerenciar alterações de estado de objetos por meio de uma série de propriedades mutáveis ​​... isso é um pouco mais complicado do que as profundezas do entendimento "Olá, mundo". 20 anos depois, estou muito surpreso que o ponto alto da programação do século XX seja escrever métodos getX e setX (que tediosos) sobre todos os atributos de um objeto sem nenhuma estrutura. Está apenas a um passo de acessar diretamente propriedades públicas com 100% de mutabilidade.
Darrell Teague

12

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.


Ponto excelente. Para ser tudo para todas as pessoas, essas estruturas têm pouca escolha, a não ser cair no menor denominador comum, usar proxies baseados em reflexão e obter / definir seu caminho para a flexibilidade. É claro que isso cria sistemas com poucas ou nenhuma regra de transição de estado ou um meio comum pelo qual implementá-las, infelizmente. Agora, não tenho muita certeza de que, depois de muitos projetos, o que é melhor para conveniência, extensibilidade e correção. Eu costumo pensar em um híbrido. Qualidade dinâmica do ORM, mas com algumas definições para quais campos são obrigatórios e que alterações de estado devem ser possíveis.
Darrell Teague

9

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 AllTruckscontenha 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ável BobsTruckpode 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 que AllTrucksagora 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 BobsTrucktiver uma referência direta ao objeto # 24601, a menos que o código que atualiza AllTruckstambém seja atualizado BobsTruck, ele deixará de representar o estado atual do caminhão de Bob. Observe ainda que, a menos que BobsTruckseja armazenado em alguma forma de objeto mutável, a única maneira que o código que atualiza AllTruckspoderia atualizá-lo seria se o código fosse explicitamente programado para isso.

Se alguém quiser usar BobsTruckpara observar o estado do caminhão de Bob e ainda manter todos os objetos imutáveis, poderia BobsTruckser uma função imutável que, dado o valor que AllTruckstem 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 emAllTruckse, 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 usar CompareExchangepara atualizar AllTrucksapenas se não tiver sido alterado e responder a uma falhaCompareExchangeregenerando 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, umCompareExchangeloop 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.


8

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 .


7

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:

  • 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

As pessoas estão usando objetos mutáveis, pelo que entendi, porque ainda estão misturando OOP com programação procedural imperativa.


5

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 :

  1. Os metadados de reflexão dos construtores Java não retêm os nomes dos argumentos.
  2. Os construtores Java (e métodos) não possuem parâmetros nomeados (também chamados de rótulos), portanto, fica confuso com muitos parâmetros.
  3. Ao herdar outro objeto imutável, é necessário chamar a ordem apropriada dos construtores. Isso pode ser bastante complicado, a ponto de desistir e deixar um dos campos não final.
  4. A maioria das tecnologias de ligação (como Spring MVC Data Binding, Hibernate, etc ...) funcionará apenas com construtores padrão sem argumento (isso ocorre porque nem sempre as anotações existem).

Você pode atenuar os nºs 1 e 2 usando anotações como @ConstructorPropertiese criando outro objeto construtor mutável (geralmente fluente) para criar o objeto imutável.


5

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.


4

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.


4

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.


1

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 .


7
Estado imutável ainda é estado.
Jeremy Heiler

3
@ JeremyHeiler: Verdade, mas é o estado de algo que é mutável. Se nada sofrer mutação, haverá apenas um estado, que é a mesma coisa que nenhum estado.
precisa saber é o seguinte

1

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.


1

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.


3
isso é apresentado como se a mutabilidade e as alterações após a instanciação fossem a única maneira de definir vários valores. Por uma questão de completude, observe que o padrão Builder oferece recursos iguais ou ainda mais poderosos e não requer sacrifício da imutabilidade. new ReportBuilder().font("Arial").orientation("landscape").build()
Gnat

1
"Não seria prático ter uma assinatura de construtor muito longa.": Você sempre pode agrupar parâmetros em objetos menores e passar esses objetos como parâmetros para o construtor.
Giorgio

1
@cHao E se você quiser definir 10 ou 15 atributos? Também não parece bom que um método chamado withFontretorne a Report.
Tulains Córdova

1
No que diz respeito à definição de 10 ou 15 atributos, o código para fazê-lo não seria estranho se (1) Java soubesse facilitar a construção de todos esses objetos intermediários e se (2) novamente, os nomes fossem padronizados. Não é um problema com imutabilidade; é um problema com o Java não saber como fazê-lo bem.
cHao 27/12/12

1
@cHao Fazer uma cadeia de chamadas para 20 atributos é feio. Código feio tende a ser um código de baixa qualidade.
Tulains Córdova

1

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:

  1. Muitos programadores aprenderam a programar usando um paradigma imperativo (procedural ou orientado a objetos); portanto, a mutabilidade é sua abordagem básica para definir a computação, ou seja, eles não sabem quando e como usar a imutabilidade porque não estão familiarizados com ela.
  2. Muitos programadores se preocupam com o desempenho muito cedo, ao passo que geralmente é mais eficaz se concentrar em escrever um programa funcionalmente correto e depois tentar encontrar gargalos e otimizá-lo.

1
A questão não é apenas velocidade. Existem muitos tipos de construções úteis que simplesmente não podem ser implementadas sem o uso de tipos mutáveis. Entre outras coisas, a comunicação entre dois threads requer que, no mínimo absoluto, ambos tenham uma referência a um objeto compartilhado, no qual um possa colocar dados e o outro possa lê-lo. Além disso, é semanticamente muito mais claro dizer "Alterar propriedades P e Q deste objeto" do que dizer "Pegue este objeto, construa um novo objeto que seja igual a ele, exceto pelo valor de P, e então um novo objeto que é assim, exceto Q ".
Supercat 22/08

1
Eu não sou especialista em FP, mas a comunicação de encadeamento AFAIK (1) pode ser alcançada criando um valor imutável em um encadeamento e lendo-o em outro (novamente, AFAIK, esta é a abordagem Erlang) (2) AFAIK a notação para definir uma propriedade não muda muito (por exemplo, no valor do registro Haskell setProperty corresponde ao Java record.setProperty (value)), apenas a semântica muda porque o resultado da configuração de uma propriedade é um novo registro imutável.
Giorgio

1
"ambos devem ter uma referência a um objeto compartilhado no qual um pode colocar dados e o outro pode lê-lo": mais precisamente, você pode tornar um objeto imutável (todos os membros const em C ++ ou final em Java) e definir todo o conteúdo no construtor. Em seguida, esse objeto imutável é transferido do encadeamento produtor para o encadeamento consumidor.
Giorgio

1
(2) Já listei o desempenho como uma razão para não usar o estado imutável. Em relação a (1), é claro que o mecanismo que implementa a comunicação entre os threads precisa de algum estado mutável, mas você pode ocultar isso do seu modelo de programação, veja, por exemplo, o modelo do ator ( en.wikipedia.org/wiki/Actor_model ). Portanto, mesmo que em um nível mais baixo você precise de mutabilidade para implementar a comunicação, é possível ter um nível de abstração superior no qual você se comunica entre threads, enviando objetos imutáveis ​​para frente e para trás.
Giorgio

1
Não tenho certeza de entendê-lo completamente, mas mesmo uma linguagem funcional pura em que todo valor é imutável, há uma coisa mutável: o estado do programa, ou seja, a pilha de programas atual e as ligações variáveis. Mas este é o ambiente de execução. De qualquer forma, sugiro que discutamos isso no chat em algum momento e depois limpe as mensagens desta pergunta.
Giorgio

1

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.


1

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:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

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.


este não parece acrescentar nada substancial sobre os pontos feitos e explicado em antes 24 respostas
mosquito

@gnat Qual das respostas anteriores destaca que a maioria dos idiomas tradicionais não oferece suporte decente à imutabilidade? Eu acho que esse ponto simplesmente não foi esclarecido (verifiquei), mas esse é um obstáculo muito importante para o IMHO.
Hans-Peter Störr

este, por exemplo, explica detalhadamente esse problema em Java. E pelo menos 3 outras respostas indiretamente se relacionam com ela
mosquito

0

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:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

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.


1
Ainda não consigo encontrar onde li isso sobre as origens da OOP, mas, de acordo com a Wikipedia , algum Sistema Integrado de Informações Regionais de uma grande empresa de transporte de contêineres OOCL está escrito em Smalltalk.
Alexey

-2

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.


5
finalé na verdade cerca de 1/4 de um passo em direção à imutabilidade. Não vendo por que você mencionou.
cHao

você realmente leu meu post?
Ralf H

Você realmente leu a pergunta? :) Não tinha absolutamente nada a ver final. Trazê-lo nesse contexto evoca uma mistura realmente estranha de final"mutável", quando o objetivo finalé evitar certos tipos de mutação. (BTW, não meu
voto negativo

Não estou dizendo que você não tem um ponto válido aqui em algum lugar. Estou dizendo que você não está explicando muito bem. Eu meio que vejo onde você provavelmente está indo, mas você precisa ir mais longe. Como está, apenas parece confuso.
cHao 23/12/12

1
Na verdade, existem muito poucos casos em que um objeto mutável é necessário . Há casos em que o código seria mais claro ou, em alguns casos, mais rápido, usando objetos mutáveis. Mas considere que a grande maioria dos padrões de design são basicamente apenas uma versão meia-boca da programação funcional para linguagens que não a suportam nativamente. Parte divertida disso é que o FP requer apenas mutabilidade em locais muito selecionados (ou seja, onde os efeitos colaterais devem ocorrer), e esses locais geralmente são muito limitados em número, tamanho e escopo.
cHao 27/12/12
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.