Um objeto é considerado imutável quando seu estado interno, após ser criado, não muda. Em alguns casos o objeto é considerado imutável mesmo que um atributo interno mude, mas o estado pareça imutável de um ponto de vista externo. Isso pode acontecer em operação que sejam lazy, por exemplo.
O uso de objetos imutáveis traz muitas vantagens e vários tipos de problemas simplesmente desaparecem.
Nesse artigo vou mostrar como criar objetos imutáveis, suas vantagens e como utiliza-los no dia a dia.

Vantagens e mais vantagens

  • Objetos imutáveis são confiáveis pois eles nunca serão alterados.
  • Objetos imutáveis são thread-safe, então você não terá problemas com sincronização nem aqueles bugs aleatórios difíceis de resolver quando várias threads alteram o mesmo objeto.
  • Imutabilidade torna o código mais fácil de escrever, entender e dar manutenção. Também evita aqueles bugs que você precisa investigar o sistema inteiro para descobrir onde o objeto está sendo alterado.
  • Objetos imutáveis podem ser cacheados, da mesma forma que o Java faz com as Strings
  • Objetos imutáveis são ótimos candidatos a chave de Map e elemento de Set.

Estratégia para definir objetos imutáveis

  1. Defina todas as suas variáveis como private e final. Dessa forma garantimos que as variáveis não serão alteradas após o objeto ser criado
  2. Não utilize métodos setter, afinal suas variáveis serão final.
  3. Caso seu objeto receba um objeto mutável, não permita que ele seja alterado. Crie uma cópia do objeto e não permita acesso direto a ele, não retorne a referência, nada de “getObjetoMutavel()”. O estado dos objetos referenciados não deve mudar.
  4. Crie sua classe como final. Dessa forma garantimos que o objeto não terá subclasses e, portanto, não terá seus métodos sobrescritos.
public final class Quadrado{
    private final double opacidade;
    private final double tamanho;
    public Quadrado(double tamanho, double opacidade) {
        this.opacidade = opacidade;
        this.tamanho = tamanho;
    }
    public double getOpacidade() {
        return opacidade;
    }
    public double getTamanho() {
        return tamanho;
    }
 }

Parâmetros opcionais

No exemplo anterior temos um objeto imutável com todos os seus parâmetros no construtor. Mas se alguns dos nossos parâmetros forem opcionais, a opacidade por exemplo, teremos duas opções: criar um construtor para cada situação ou passar todos os valores sempre que o objeto for construído. Imagine fazer isso com vários parâmetros, seria extremamente improdutivo e chato.

public Quadrado(double tamanho) {...}
public Quadrado(double tamanho, double opacidade) {...}
public Quadrado(double tamanho, double opacidade, Cor cor, ...) {...}
new Quadrado(10, 0, branco, ...);

Para evitar essas duas situações podemos utilizar o builder pattern para criar o objeto.

Imutabilidade com o Builder pattern

Faremos algumas alterações na classe Quadrado para utilizar o builder. Teremos um construtor private recebendo o builder como parâmetro. Dessa forma forçamos a construção do objeto através do builder. Também incluiremos o método toBuilder. Explicarei a função desse método mais a frente.
Criaremos o nosso builder como uma inner class contendo dois construtores. Um construtor, public, contendo todos os parâmetros obrigatórios e o outro, private, que recebera o Quadrado como parâmetro. Esse construtor será utilizado para copiar o objeto e será acessado apenas pelo método toBuilder, para padronizar a cópia.
Os métodos do builder terão os mesmos nomes das variáveis sem o prefixo set, tão comum em POJOs. O retorno dos métodos será o próprio builder, dessa forma criamos uma interface fluente.
E para criar nosso Quadrado temos o método build.

package test;
public class Quadrado {
    private final double opacidade;
    private final double tamanho;
    private Quadrado(Builder builder) {
        this.opacidade = builder.opacidade;
        this.tamanho = builder.tamanho;
    }
    public double getOpacidade() {
        return opacidade;
    }
    public double getTamanho() {
        return tamanho;
    }
    public Builder toBuilder() {
        return new Builder(this);
    }
    public static class Builder {
        private double opacidade;
        private double tamanho;
        public Builder(double tamanho) {
            this.tamanho = tamanho;
        }
        private Builder(Quadrado quadrado) {
            tamanho = quadrado.tamanho;
            opacidade = quadrado.opacidade;
        }
        public Builder opacidade(double opacidade) {
            this.opacidade = opacidade;
            return this;
        }
        public Builder tamanho(double tamanho) {
            this.tamanho = tamanho;
            return this;
        }
        public Quadrado build() {
            return new Quadrado(this);
        }
    }
}

A interface fluente do builder deixa o código simples e legível.

Quadrado quadrado = new Quadrado.Builder(10)
        .opacidade(.2)
        .build();

O método toBuilder fornece uma forma simples e padronizada para copiar e editar um objeto imutável. No exemplo abaixo copiamos o objeto quadrado e alteramos a propriedade opacidade para 0.3.

Quadrado quadradoNovo = quadrado
        .toBuilder()
        .opacidade(.3)
        .build();

Conclusão

Imutabilidade torna o código mais fácil de escrever, entender e dar manutenção. Utilize objetos imutáveis sempre que possível, eles evitam vários tipos de bugs.
Utilizando o Builder pattern temos uma forma padronizada para criar, copiar e editar objetos imutáveis.
 
Este artigo foi publicado primeiro em: brgarcia.com

Autor

Bruno Garcia é desenvolver na Bluesoft há mais de 4 anos. Atualmente, ele lidera uma grande equipe de desenvolvedores que atua no módulo Financeiro do software da Bluesoft.

Deixe aqui o seu comentário