Eu tive uma discussão recentemente com a minha equipe a respeito de classes imutáveis. Alguns membros concordaram que classes imutáveis simplificam o desenvolvimento e tornam o código mais seguro, mas outros argumentaram que suas desvantagens se sobrepõem às suas vantagens. Outra questão que surgiu foi sobre quando usá-las e quando não usá-las.
Visão GeralAntes que eu possa abordar cada uma das questões acima em detalhas, eu vou começar com uma uma visão geral declasses imutáveis. Uma classe é dita imutável se o estado de um objeto dsta classe não pode ser alterado depois que o objeto é criado. Em Java, você consegue isso se não fornecer nenhum método "setter", ou, mais genericamente, nenhum método público que possa mudar o valor de um atributo. Você pode reforçar a condição de imutável se fizer com que todos os atributos sejam
final. Normalmente você também vai querer fazer com que a própria classe também seja
final, para evitar que um usuário mal intencionado ou descuidado não estenda a classe e a torne mutável.
Os campos de uma classe imutável devem ser ou eles próprios imutáveis, ou não deve ser expostos para o exterior da classe. O objetivo é evitar que alguém modifique um atributo privado de uma classe imutável. Se você quiser expor um campo não imutável de uma classe imutável através de um "getter", por exemplo, você deveria fazer uma cópia do objeto e retornar a cópia ao invés do objeto original. A mesma coisa é válida quando um objeto mutável é passado para o construtor de uma classe imutável. Neste caso, você deve se certiricar de fazer uma cópia do objeto antes de armazená-la.
Exemplos de classes imutáveis no Java são java.lang.String and as classes
wrapper como java.lang.Double, java.lang.Integer, etc. Um exemplo de uma classe que não é imutável mas deveria ser é a classe java.util.Date.
VantagensA principal vantagem de classes imutáveis é que você
não precisa fazer cópias de segurança de objetos. Digamos que você está escremento um método que recebe uma String como parâmetro e seta como atributo da classe que contém o método. Você não precisa fazer uma cópia de segurança da String. Uma vez que a classe String é imutável, não há risco de alguém mudar a String fora da sua classe. De maneira similar, você não precisa fzer uma cópia da String quando está retornando através de um getter.
Mas se você escrever um método que recebe um objeto Date como parâmetro, ou um getter que retorna um atributo Date, você provavelmente vai querer fazer uma cópia de segurança do objeto Date, ou você pode ter bugs complexos quando alguém muda inadvertidamente o objeto Date que seu objeto retornou.
Veja um exemplo de bug que pode acontecer neste caso (retirado
daqui):
task1.setStartDate(new Date("1 Jan 98");
task2.setStartDate(task1.getTaskDate());
// em algum lugar da classe Task
void delay(int delayDays) {
_startDate.setDate(_startDate.getDate() + delayDays);
}
// em um lugar qualquer...
task2.delay(5);
E você vai descobrir que o campo _startDate do objeto task1 mudou.
Uma outra vantagem é que classes imutáveis são inerentemente
thread safe. Uma vez que nenhum campo pode ser modificado, você pode compartilhar objetos entre várias threads sem ter que sincronizar os acessos ao objeto.
Mais uma vantagem de classes imutáveis é que elas automaticamente
reduzem o acoplamento. Mesmo que uma instância da classe String seja compartilhada entre vários outros objetos, não há acoplamento entre eles porque não há estado ocmpartilhado. Um objeto jamais pode afetar o estado de outro objeto através desta String.
DesvantagensA principal desvantagem de classes imutáveis é que podem ser chatas de usar em iterações onde seus valores devem ser constantemente atualizados. Neste caso, para cada iteração você tem que criar uma nova instância da classe com os valores atualizados. Isto foi citado na discussão que tive com minha equipe como uma razão para não usar classes imutáveis.
Este problema no entanto pode ser facilmente resolvido através da criação de uma
classe mutável auxiliar. De volta ao exemplo da classe String, o Java fornece a classe StringBuffer que você deve usar quando tem que fazer atualizações freqüentes em uma String. Mas o bom senso dita que a utilização da classe mutável auxiliar deve ser tão localizada quanto possível. Você começa com uma String, aí você cria o StringBuffer para ser atualizado em um loop, e ao fim do loop você converte de volta para uma String e descarta o StringBuffer. Normalmente não se passa StringBuffers de um lado para o outro entre classes ou os seta como atributos de outras classes.
Quando utilizá-lasUma situação em que se deve sempre usar classes imutáveis é quando a classe modela um
value object. Value Objects são coisas como Strings, Dates, Numbers, etc. Você provavelmente tem outros Value Objects específicos dos seu domínio. Value Objects devem
sempre ser imutáveis. Martin Fowler afirma
aqui:
"Se você está usando um Value Object que é mutável, trate-o como se ele fosse imutável. Você pode não perceber o porquê, mas você vai economizar muito tempo e dinheiro.". Value Objects são um tópico que merecem uma discussão à parte, então não vou entrar em detalhes aqui, mas você pode olhar os links acima para maiores informações.
Você não deve entrar em uma loucura de classes imutáveis. Classes que modelam entitades de negócio não devem ser imutáveis, uma vez que elas têm atributos que mudam através do tempo e há uma razão legítima para compartilhar a mesma instância de uma entidade de negócio entre classes.
Uma forma de identificar value objects é se perguntando se duas instâncias de uma mesma classe com os mesmos valores em todos os atributos são equivalentes. Se a resposta for sim, sua classe é provavelmente um Value Object. Duas entidades de negócio podem ter os mesmos valores nos seus atributos mas ainda assim representar diferentes entidades e ter chaves primárias diferentes.
ConclusãoClasses imutáveis melhoram muito a qualidade do seu código uma vez que evitam bugs complexos e reduzem acoplamento. Elas são recomendadas especialmente para value objects, uma vez que eles são passados de um lado para o outro com freqüencia. Entidades de negócio não devem ser imutáveis.