24 de set. de 2013

Métodos Estáticos de Fabricação: uma alternativa aos construtores, mais elegante e comunicativa

Métodos Estáticos e Fabricação, em inglês Static Factory Methods, servem como alternativa mais inteligível aos construtores.

Construtores são necessários, obviamente, mas eles tem um pequeno problema: não tem nome.

Considere uma classe Dado e um objeto instanciado a partir dela. Sabendo que cada Dado tem número de lados determinado, quantos lados tem este dado:

Dado dado = new Dado();
dado.joga();
System.out.println(dado);

Digamos que a implementação de Dado preveja como padrão 6 lados e dê a opção de construir um Dado com um número arbitrário de lados (sem validações para brevidade):

package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    public Dado() {        
        this(6); // cria um dado de seis lados por padrão
    }    

    public Dado(int lados) {
        this.lados = lados;
    }

    public int getNumero() { return numero; }

    public int getLados()  { return lados; }

    public void joga() { numero = (int) (random() * lados + 1); }

    @Override
    public String toString() { return String.valueOf(numero); }
    
}

Mesmo que instanciemos o Dado passando lados, quem lê apenas a construção e não tem acesso ao código-fonte não pode afirmar com certeza o significado do parâmetro, veja a seguir:

Dado dado = new Dado(10);
dado.joga();
System.out.println(dado);

É intuitivo pensar que Dado(10) é um Dado com 10 lados, mas não é algo implícito.

Embora eu programe com mais frequência em Java, também estou atento e programo em outras linguagens, que me faz melhor como programador Java. Como assim? Bem, aprender diferentes culturas de implementação te ajudam a ver os problemas de mais pontos de vista. Um ditado presente no Zen do Python diz:Explicit is better than implicit

Nesta implementação do Dado, o construtor é implícito, ou seja, nós deduzimos que Dado(10) é um Dado com *dez* lados pois está implícito.

Que tal tornar explícito? Não é complicado, basta adicionar um método fábrica. Veja exemplo:


package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    // método para fabricação de Dados
    public static Dado lados(int lados) {
        return new Dado(lados);
    }

    public Dado() {        
        this(6); // cria um dado de seis lados por padrão
    }    

    public Dado(int lados) {
        this.lados = lados;
    }

    public int getNumero() { return numero; }

    public int getLados()  { return lados; }

    public void joga() { numero = (int) (random() * lados + 1); }

    @Override
    public String toString() { return String.valueOf(numero); }
    
}

A grande vantagem de um método fábrica é que ele tem nome e com nome ele demonstra a intenção explicitamente. Veja uma situação de uso:

Dado dado = Dado.lados(10); // bem mais claro, certo
dado.joga();
System.out.println(dado);

Ainda é possível predefinir construções comuns com métodos sem parâmetros, por exemplo, dados de 6, 10 e 20 lados.

package aps.livro;

import static java.lang.Math.random;

public class Dado {
    
    private int numero;
    private final int lados;

    public static Dado lados(int lados) {
        return new Dado(lados);
    }

    // métodos de fabricação com parâmetro constante
    public static Dado deSeisLados()  { return lados(6); }

    public static Dado deDezLados()   { return lados(10); }

    public static Dado deVinteLados() { return lados(20); }

    // ... restante omitido

Veja a clareza que fica:

Dado dado = Dado.deDezLados(); // bem mais claro que: new Dado(10)
dado.joga();
System.out.println(dado);

Mais detalhes podem ser vistos no livro do Josh Bloch:
http://www.submarino.com.br/produto/6800218/livro-java-efetivo

Vale a pena gasta um minuto para ler o Zen do Python:
http://www.python.org/dev/peps/pep-0020/

O código-fonte dos exemplos está disponível aqui: https://github.com/marciojrtorres/livro-aps/tree/master/static_factory_method