quarta-feira, 25 de junho de 2014

Design Pattern Fluente Interface

Home

Design Pattern Fluente Interface

Motivação

Penso que um programador, assim como escritores, jornalistas e legisladores, precisam ter a máxima preocupação em se fazer entender quanto ao conteúdo que escreve.

Imagine uma lei que tem dupla interpretação, ou mesmo não tem como ser interpretada (ainda bem que nenhuma lei brasileira é assim. sqn.). Uma notícia que não informa. Uma estória sem pé e sem cabeça.

Quando se trata de escritores, jornalistas e legisladores parece absurdo, né? Mas tem muito programador que só consegue passar o seu recado para o computador, porque para outro programador é quase impossível saber o que o cidadão estava pensando quando fez o código.

Para ajudar um pouco na legibilidade de um código escrito com POO irei apresentar o padrão Fluente Interface, o qual irei chamar de aqui em diante de Interfaces Fluentes.

Explanação

O legal das Interfaces Fluentes é que a sua utilidade é diretamente proporcional à sua simplicidade.

A seguir mostrarei exemplos de uso do padrão. Em seguida mostrarei com se implementa.

public class Teste {
    public static void main(String[] args) {
        SQL sql = new SQL();
        
        String query = 
                sql.select("nome").from("pessoa").where("idade > 18").getSql();
        
        System.out.println(query);
//Saída: SELECT nome FROM pessoa WHERE idade > 18;
    } 
}

public class Teste {
    public static void main(String[] args) {
        Pessoa helio = new Pessoa();
        
        helio.cujoNomeEh("Helio Albano de Oliveira").nascidoEm("Jaguapitã-PR")
                .morandoHojeEm("Londrina-PR").formouSeEm("Ciência da Computação")
                .pelaInstituicao("Universidade Estadual de Londrina");
        
        System.out.println(helio.getInformacoes());
//Saída: Helio Albano de Oliveira nasceu em Jaguapitã-PR, hoje mora em Londrina-PR. Helio Albano de Oliveira Formou-se em Ciência da Computação pela Universidade Estadual de Londrina.
    } 
}

E aí, dá pra imaginar o que cada código faz?

Se ainda não deu para perceber, irei fazer os mesmos códigos acima, mas agora da maneira tradicional.

public class Teste {
    public static void main(String[] args) {
        SQL sql = new SQL();

        sql.select("nome");
        sql.from("pessoa");
        sql.where("idade > 18");
        String query = sql.getSql();
        
        System.out.println(query);
    }
}

public class Teste {
    public static void main(String[] args) {
        Pessoa helio = new Pessoa();
        
        helio.cujoNomeEh("Helio Albano de Oliveira");
        helio.nascidoEm("Jaguapitã-PR");
        helio.morandoHojeEm("Londrina-PR").formouSeEm("Ciência da Computação");
        helio.pelaInstituicao("Universidade Estadual de Londrina");
        
        System.out.println(helio.getInformacoes());
    } 
}

E agora, percebeu?

Nos dois últimos exemplos, isto é, no modo tradicional, a leitura fica meio enroscada, enquanto que nos dois primeiros exemplos a leitura do código é mais fluída.

Mas como implementar isso?

Basta fazer os métodos do objeto retornarem uma instancia do próprio objeto a qual pertencem.

O retorno do método é um objeto, dessa forma posso chamar métodos deste objeto retornado.

No exemplo do sql, o objeto sql chama o método select, que por sua vez retorna quem? Isso mesmo, o próprio objeto sql. Assim estamos prontos pra começar novamente.

Se ainda está um pouco confuso, veja os códigos abaixo que tudo ficará muito claro.

public class SQL {
    private String columns = "";
    private String table = "";
    private String conditions = "";
    
    public SQL select(String columns) {
        this.columns = columns;
        return this;
    }
    
    public SQL from(String table) {
        this.table = table;
        return this;
    }
    
    public SQL where(String conditions) {
        this.conditions = conditions;
        return this;
    }
    
    public String getSql() {
        String query = "SELECT " + this.columns + " ";
        query += "FROM " + this.table + " ";
        query += "WHERE " + this.conditions + ";";
        return query;
    }
}

    public class Pessoa {
    private String nome = "";
    private String naturalidade = "";
    private String cidadeAtual = "";
    private String formacao = "";
    private String instituicao = "";
    
    public Pessoa cujoNomeEh(String nome) {
        this.nome = nome;
        return this;
    }
    
    public Pessoa nascidoEm(String cidade) {
        this.naturalidade = cidade;
        return this;
    }
    
    public Pessoa morandoHojeEm(String cidade) {
        this.cidadeAtual = cidade;
        return this;
    }
    
    public Pessoa formouSeEm(String curso) {
        this.formacao = curso;
        return this;
    }
    
    public Pessoa pelaInstituicao(String instituicao) {
        this.instituicao = instituicao;
        return this;
    }
    
    public String getInformacoes() {
        String info = this.nome + " nasceu em ";
        info += this.naturalidade + ", hoje mora em ";
        info += this.cidadeAtual + ". ";
        info += this.nome + " Formou-se em ";
        info += this.formacao + " pela ";
        info += this.instituicao + ".";
        
        return info;
    }
}

Conclusão

Que bonito. Pena que só dá para fazer em linguagens fortemente tipadas, né?

Não senhorito(a). De brinde vai um exemplo em ruby para comprovar.

class SQL
  @colums = ''
  @table = ''
  @conditions = ''

  def select colums
    @colums = colums
    self
  end

  def from table
    @table = table
    self
  end

  def where conditions
    @conditions = conditions
    self
  end

  def sql
    "SELECT #{@colums} FROM #{@table} WHERE #{@conditions};"
  end
end

query = SQL.new

query.select('nome').from('pessoa').where('idade > 18')

puts query.sql
#Saída
#SELECT nome FROM pessoa WHERE idade > 18;

Como todo padrão, este também só deve ser usado quando for conveniente. Nada de pegar a febre da paternite e querer usar padrões em tudo :)