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 :)

quarta-feira, 18 de junho de 2014

Design Pattern - Strategy

Motivação

Para se fazer um bom uso de orientação a objetos é necessários que se respeite alguns princípios. Um desses princípios é o open close ou, no bom português, aberto fechado.

Mas o que significa esse tal princípio aberto fechado?

A grosso modo, esse princípio diz que devemos programar de forma que nosso código fique aberto para extensão e fechado para modificação.

Ok, acho que entendi. Mas qual é a relação entre o padrão strategy e o princípio open close?

Para podermos visualizar a ligação entre o strategy e o princípio open close vamos desenvolver um exemplo sem o uso do strategy. O vínculo entre o princípio open close e o padrão strategy ficará claro quando fizermos a refatoração do código com o uso do padrão strategy.

Explanação

Imagine que você está programando um sistema de vendas. Já está quase tudo pronto, só faltando programar a forma de pagamento.

Como o cliente, a princípio, só vai fazer vendas à vista a tarefa parece simples. Os trechos de código (em java) a seguir resolvem o problema.

public class Sales {
  private List dataBaseFake = new ArrayList();

  public void payment(float total) {
    Instalment instalment = new Instalment(total, DateTime.now());
    this.dataBaseFake.addAll(instalment);
  }
}
public class Main {
  public static void main(String[] args) {
    Sales sale = new Sales();

    sale.payment(1000.00f);
  }
}

Legal, problema resolvido, mas agora o cliente quer a opção de fazer o pagamento em três vezes sem juros.

Podemos abrir o método payment e inserir um if para avaliar o novo requisito.

Isso funciona? Como visto no código abaixo, vemos que funciona. Mas perceba que para fazer isso tivemos que abrir o método e fazer uma mudança. Essa abertura para mudança vai contra o princípio open close.

public class Sales {
  private List dataBaseFake = new ArrayList();
  private DateTime today = DateTime.now();

  public void payment(float total, String condition) {
    if (condition.equals("Avista")) {
      Instalment instalment = new Instalment(total, today);
      this.dataBaseFake.addAll(instalment);
    } else if (condition.equals("DuasVezesSemJuros")) {
      for (int i = 0; i < 3; i++) {
        Instalment instalment =
          new Instalment(total/3, today.plusMonths(i));
        instalments.add(instalment);
      }
      this.dataBaseFake.addAll(instalments);
    }
  }
}
public class Main {
  public static void main(String[] args) {
    Sales sale = new Sales();
    sale.payment(1000.00f, "DuasVezesSemJuros");
  }
}

Há a possibilidade do cliente pedir para implementar mais um tipo de condição de pagamento?

Eu diria que a probalidade de isto acontecer é de 110%.

E aí, para cada pedido de mundança, abrimos, mudamos e refazemos todos os testes e de quebra deixamos o nosso código mais confuso? Não, né? É aí que entra o padrão strategy.

Como percebemos, no problema que estamos resolvendo, a quantidade de estratégias de pagamento aumentou e tende a aumentar ainda mais (5 vezes sem juros, 12 vezes com juros, 48 vezes com JUROS, ...).

Para resolver o problema com o padrão Strategy vamos criar a Interface PaymentStrategy e vamos definir para esta Interface o método calculatedInstalments.

public interface PaymentStrategy {
  public List calculatedInstalments(float total);
}

Agora reescrevemos a classe Sales como a seguir:

public class Sales {
  private List dataBaseFake = new ArrayList();

  public void payment(float total, PaymentStrategy ps) {
    this.dataBaseFake.addAll(ps.calculatedInstalments(total));
  }
}

Agora precisamos escrever o código das condições de pagamento solicitadas pelo cliente. Porém, para que haja compatibilidade entre as implementações de cada forma de pagamento, todas as implementações devem ter a mesma cara, isto é, todas devem implementar a mesma Interface, a Interface PaymentStrategy. Veja a implementação da condição de pagamento Avista.

public class Avista implements PaymentStrategy {

  @Override
  public List calculatedInstalments(float total) {
    List instalments = new ArrayList();

    Instalment instalment = new Instalment(total, DateTime.now());

    instalments.add(instalment);
    return instalments;
  }
}

Agora veja a implementação da condição de pagamento TresVezesSemJuros.

public class TresVezesSemJuros implements PaymentStrategy {

  final static int MAX_INSTALMENTS = 3;

  @Override
  public List calculatedInstalments(float total) {
    List instalments = new ArrayList();
    DateTime today = DateTime.now();

    for (int i = 0; i < MAX_INSTALMENTS; i++) {
        Instalment instalment =
                new Instalment(total/MAX_INSTALMENTS, today.plusMonths(i));
        instalments.add(instalment);
    }
    return instalments;
  }
}

Agora, um exemplo de uso:

public class Main {
  public static void main(String[] args) {
    Sales sale = new Sales();

    sale.payment(1000.00f, new Avista());

    Sales sale2 = new Sales();

    sale2.payment(1000.00f, new TresVezesSemJuros());

  }
}

Não importa quantos métodos de pagamento surgirem de agora em diante, a classe Sales não precisa mais ser aberta para mudanças, isto é, está fechada para mudanças. Mas note que, ao mesmo tempo, a classe está aberta para extensão, isto é, pode-se aumentar o número de formas de pagamento indefinidamente.

Conclusão

Gostaria que ficasse claro que o padrão de projeto aqui apresentado não é de como se resolver o problema de quantidade de prestações que uma loja oferece. O padrão que deve ser observado é de um problema que pode ser resolvido com várias estratégias diferentes.

Um exemplo de uso de estratégias é o de um jogo do tipo Counter Strike, onde um mesmo jogador pode usar várias estratégias, isto é, pode em dado momento usar uma faca, em outra circunstância pode usar uma metralhadora ou outros tipos de armas conforme demanda a situação.

Imagine que a comunidade que joga esse jogo nos moldes do Counter Strike quer como arma o martelo do Thor. Para a equipe de desenvolvimento do jogo, seria muito arriscado abrir um código que já funciona perfeitamente, enfiar um if lá no meio e implementar o martelo do Thor. Mas se a equipe tem a possibilidade de apenas extender o jogo, isto é, apenas implementar o martelo do Thor sem precisar mexer no que já funciona, isso é bem mais eficiente e seguro.

sábado, 14 de junho de 2014

Axiomas dos números naturais

Motivação

O que você diria se lhe perguntassem o que é número natural?

Bom, não sei você, mas eu diria que são os números usados para contar coisas não fracionadas. Por exemplo: 1 laranja, 2 gatos, 3 livros e por aí vai.

Mas será que existe alguma definição formal, ou mesmo um conjunto de axiomas que definem o que é número natural? A resposta é: sim.

Explanação

O conjunto de regras a seguir é conhecido, em uma livre tradução, como Aritmética de Peano.

Regra do valor inicial
Existe um objeto especial chamado 0, e 0 é um número natural.
Regra do sucessor
Para cada número natural n existe um único outro número natural que é o sucessor de n, este outro número pode ser definido como s(n)
Regra do predecessor
Zero não é sucessor de número natural algum, e todo número natural com exeção do 0 é sucessor de um outro número natural, este outro número natural é donominado predecessor. Digamos que existam dois números naturais, a e b; se b é sucessor de a, então a é predecessor de b.
Regra da unicidade
Nunca dois números naturais diferentes terão o mesmo sucessor.
Regra da igualdade
Números podem ser comparados por igualdade. Esta regra é composta por três sub-regras:
Igualdade é reflexiva
Todo número é igual a si mesmo.
Igualdade é simétrica
Se um número a é igual um número b, então b=a.
Igualdade é transitiva
Se a=b e b=c, então a=c.
Regra da indução
Para uma dada sentença P, P é verdade para todo número natural se:
  1. P é verdade sobre 0, isto é, P(0) é verdade.
  2. Se P é verdade para um número natural n (P(n) é verdade), então pode-se provar que P é verdade para o sucessor s(n) de n, isto é, P(s(n)) é verdade.