Design, Design Patterns

Sua API fala? – Interfaces fluentes

Pra começo de conversa eu chego e digo logo que não gosto da forma como Java trata datas e calendários. Há alguns anos atrás eu lia alguns programadores ‘rock-stars’ dizerem que era uma API mal feita e tudo mais. Mas eu não entendia o porquê. Bom, há algum tempo eu tenho entendido a razão disso. Tirando o fato da classe Date e da classe Calendar serem mutáveis (inclusive essa questão já é clichê, falo disso em outro post – prometo), as operações não são nada elegantes. Trabalhosas, verbosas, exigem um monte de classes pra formatar tipo DateFormat, NumberFormat (no caso de números), apresenta problemas de performance em certos casos, difícil de estender e etc.  Todo mundo que já trabalhou um pouco com Java sabe como essa API funciona. Talvez não tenha noção do como ela é chata de trabalhar, mas sabe como funciona. Veja isso por exemplo:


//Isso tudo é pra setar uma data (12 de fevereiro de 1987)

 Calendar calendar = Calendar.getInstance();
 calendar.setTimeZone(TimeZone.getTimeZone("Universal");
 calendar.set(Calendar.DATE, 12);
 calendar.set(Calendar.MONTH, 2 - 1);
 calendar.set(Calendar.YEAR, 1987);
 Date feb12_1987 = calendar.getTime();

Um monte de linha de código pouquíssimo expressivas. Mais feio que parto de ouriço. E olhe que essa data não está sendo formatada. Se alguém quiser escrever isso no cabeçalho duma página, por exemplo? Mais trabalho ainda pra formatar em String e… haja paciência. Não estou querendo dizer que você jamais deva usar a API Dates e Calendars na vida. Mas, veja como essa API soa melhor:


//Setando a mesma data (12 de fevereiro de 1987) usando Joda-Time API

DateTime feb12_1987 = new DateTime().withDayOfMonth(12).withMonthOfYear(2).withYear(1987);

Então? Bem melhor, não? Não mudei o objeto em questão (cada método chamado retorna um novo objeto DateTime modificado), não usei variáveis static, não precisei criar um calendário pra fazer operações no meu tipo principal (no caso data) e o melhor de tudo: tudo em uma linha e facílimo de ler. Muito elegante. Esse é um belo exemplo onde a API Joda-Time usa method chaining, que é a técnica de encadear os métodos chamando-os em seguida, mas, vai além, faz o serviço de forma fluente! Repare como essa chamada parece mesmo uma frase: “nova data com dia do mês:12, com mês do ano:2 e com ano 1987.”  Alguém pode dizer que isto é, na verdade, o padrão Builder. Bom, também. Mas não apenas. Builder se preocupa apenas em construir objetos, não com legibilidade e fluência. Builder por padrão é usado com métodos set() e isso por si só já tira a fluência da chamada. Ah sim, além disso podemos fazer qualquer operação no objeto com interfaces fluentes (formatar saída, fazer conversões etc.), enquanto com Builder a idéia é apenas construir o objeto. Enfim, a API do Joda fornece uma porção de interfaces fluentes que me deixam feliz e satisfeito =D . Mas, o que exatamente é a tal da interface fluente?

Interface fluente é um estilo de escrever código no qual a função ou as funções que você deseja desempenhar são feitas de uma maneira bem mais simples do que a maneira usual e simplesmente “fluem”. “Interfaces” no sentido mais amplo da palavra , ou seja, os métodos expostos de uma dada classe. E não necessariamente interfaces Java.  Também podem ser chamadas de Interfaces Humanas, visto que o código cliente é que vai ser muito mais fácil e legível de escrever e compreender. Já pra quem escreve a API é mais difícil. Sim, mais difícil porque exige muito mais tempo pensando (às vezes muito tempo mesmo) em como prover um sistema de encadeamento de métodos que qualquer pessoa possa usar facilmente, com uma nomenclatura super clara que realmente indique qual o passo a seguir e ainda com todos os atributos de qualidade que uma boa API deve ter – alta coesão e baixo acoplamento.  Nem adianta eu ficar tentando explicar com conceitos esse estilo de API. Um exemplo vale mais do que mil palavras. E com certeza você vai entender melhor o que são de fato com exemplos. Suponha uma classe Money que representa uma quantia de dinheiro em qualquer moeda. Eu quero dar suporte aos clientes dessa interface para fazer soma de valores, subtrações de valores, conversão de moedas (tanto em símbolo que representa a moeda como em taxa de conversão) e formatação pra String. Então criei uma classe Money (me apeguei mais a fluência dela e deixei algumas potenciais falhas , por exemplo  tratamento de erros e outros, não reparem):

 


public class Money {

  public static final String REAIS = "R$";

  public static final String USDOLLARS = "U$";

  public static final String EUROS = "€";

  public static final String COMMA = ",";

  public static final String POINT = ".";

  private double value;

  private boolean usingComma;

  private static NumberFormat formatter;

  static {

    formatter = NumberFormat.getNumberInstance();

    formatter.setMinimumFractionDigits(2);

  }

  //Valor padrão

  private String currency = USDOLLARS;

  private Money(double value) {

    this.value = value;

    this.usingComma = false;

  }

  public static Money get(double value) {

    return new Money(value);

  }

  public static Money get(String value)  {

    double numValue = Double.parseDouble(value);

    return new Money(numValue);

  }

  public double value() {

    return this.value;

  }

  public long inCents() {

    return (long)this.value*100;

  }

  public String asString() {

    String formattedValue = formatValue();

    return formattedValue;

  }

  public Money in(String currentCurrency) {

    this.currency = currentCurrency;

    return this;

  }

  public Money usingComma() {

    this.usingComma = true;

    return this;

  }

  public Money convertTo(String currency) {

    this.value *= MoneyConversion.calculate(this.currency, currency);

    this.currency = currency;

    return this;

  }

  public Money plus(double amount) {

    this.value += amount;

    return this;

  }

  public Money minus(double amount) {

    this.value -= amount;

    return this;

  }

  private String formatValue() {

    String formattedValue = this.currency + formatter.format(this.value);

    if(this.usingComma) {

      if(formattedValue.contains(POINT)) {

        formattedValue = formattedValue.replace(POINT, COMMA);

      }

    }

    else {

      if(formattedValue.contains(COMMA)) {

        formattedValue = formattedValue.replace(COMMA, POINT);

      }

  }

  return formattedValue;

}

public static void main(String[] args) {

    String result1 = Money.get(10).in(REAIS).convertTo(USDOLLARS).usingComma().asString();

    System.out.println(result1);

    long result2 = Money.get(1).plus(15).plus(60).minus(50.5).inCents();

    System.out.println(result2 + " cents");

    String result3 = Money.get(0).plus(100).in(EUROS).asString();

    System.out.println(result3);

  }

}

 

“Ah!” – alguém pode dizer – “mas não era melhor fazer métodos do tipo…  new Money(10).convertFromReaisToDollarsUsingCommaAndFormatAsString ?” Veja bem, nesse caso o método viola o Princípio da Responsabilidade Única fazendo 3 coisas. Além disso, eu continuo a ter uma grande quantidade de métodos como seria no caso sem interface fluente. Imagina só fazer um método para cada combinação diferente de operações! Flexibilidade zero.

É meio estranho o fato de você retornar o próprio objeto num método modificador. Mas lembre-se que esse é um padrão de escrita diferente. Diferente de  JavaBeans, onde o objeto teria apenas seus atributos e cada um com seu get e set correspondente. Para obtermos a fluência precisamos invocar os métodos desejados sempre retornando algo que possa ser usado na próxima invocação. No caso, eu escolhi por retornar novas instâncias de Money(de forma que eles são imutáveis) exceto quando o cliente desejar pegar o resultado final das operações, quando o retorno é uma String com o valor formatado (asString()) ou a quantidade de centavos (caso queira formatar da sua própria forma). Mas como eu disse, Money é uma classe boba no sentido dos pequenos detalhes. A ênfase está nos nomes e eu não me canso de estressar esse ponto: Pare bastante pra pensar nos nomes dos métodos da sua interface fluente! Sem dúvida é bem mais trabalhoso fazer algo assim com qualidade, mas os resultados são fascinantes. Ah sim! Note que algumas linguagens tornam essa tarefa mais fácil, por exemplo Ruby e Python, tão falados hoje em dia. Em Java e C# já não é tão fácil ou os resultados não são ótimos. C++ e  C são verbosas demais, cheias de lixo sintático e tal. No mais, linguagens dinâmicas (também por serem ricas em syntax sugar) são mais produtivas pra isso também.

Mas cuidado! Não saia por aí fazendo interfaces fluentes em todo código que pegar. É uma técnica que deve ser usada com moderação e razoabilidade. Uma classe com 2 métodos e sem nenhuma chance de crescer não tem motivo de ser fluente – já é simples por natureza. Se não houver um bom range de combinações possíveis de operações também não compensa – muito trabalho pra projetar algo que não vai ser usado. E cuidado pra não ficar com mania de fluência e querer aplicar princípios de fluent interfaces em classes normais, fazendo sets retornar objetos e tal. Isso é uma violação do princípio CommandQuerySeparation. Simplesmente não funciona, ok?

Note que TDD (Test-Driven Development) cai como uma luva se você está querendo implementar Fluent Interfaces.  Tipo, primeiro você escreve a forma que o cliente deve chamar as operações (seu código não vai compilar) e depois escreva apenas código necessário para fazer seu teste compilar.  Depois refatore sua implementação para fazer o teste passar. Existe uma pá de bons artigos sobre interfaces fluentes. Eu gosto desse e desse principalmente que é baseado no post clássico do Martin Fowler. Outros exemplos de APIs Java escritas neste estilo e amplamente difundidas são JMock (para testes) e Criteria do Hibernate. Enfim, a idéia motivadora por trás disso tudo é sempre a mesma – programe não apenas para a máquina entender, mas [principalmente] para as pessoas entenderem.

Anúncios
Padrão
Design, Design Patterns, Lógica

Tua hierarquia não irá te salvar garoto – Strategy

Imagine que hoje seu chefe chegou inspirado e te deu a missão de desenvolver o protótipo de um jogo de esportes. Nesse jogo, existem vários jogadores de modalidades distintas. De forma que todos eles possuem capacidades físicas inerentes aos esportes que praticam. Tipo, um jogador de futebol consegue correr muito rápido e pular razoavelmente alto. Um jogador de vôlei, por outro lado, não corre tanto assim, mas pula muito alto. Você, como excelente desenvolvedor em Orientação a Objetos pensa: “Já matei! Mais um problema manjado de herança!”. Bom, não necessariamente está errado. Você provavelmente vai surgir com uma solução. Mas, é esta solução a melhor? É a mais flexível, mais fácil de testar e de melhor manutenção?

Vamos lá, a primeira coisa a se fazer se você quer criar uma hierarquia de tipos é criar uma classe ‘pai’. Para jogadores parece bem óbvio… hum… Jogador. Vamos assumir para fins didáticos que a classe Jogador tem apenas 2 métodos: correr() e pular(), ambos abstratos. Em seguida, bolamos as classes filhas, digamos: JogadorFutebol e JogadorHoquei. No caso, teríamos:

public abstract class Jogador {
     public abstract void correr();
     public abstract void pular();
}
public class JogadorFutebol extends Jogador {
   public void correr() {
       System.out.println("correr como jogador de futebol...");
   }
   public void pular() {
       System.out.println("Pular como jogador de futebol...");
   }
}

public class JogadorHoquei extends Jogador {
    public void correr() {
        System.out.println("Correr de patins...");
    }
public void pular() {
        System.out.println("Não pular...");
    }
}

Legal! Isso funciona! Então beleza, agora seu chefe chega e diz que seus clientes estão loucos ensandecidos para que o jogo dê suporte a tênis! Bom, agora você vai ter apenas que criar mais uma classe filha de Jogador, só que dessa vez o jogador corre muito e não pula (o cliente pediu isso especificamente =P). Daí, resolve criar a classe JogadorTenis, que… “peraí! Eu posso herdar o corre() do jogador de futebol e o pula do jogador de hóquei e vou economizar um monte de código!” Hehe, tá, mas existem alguns problemas aí. Muitas linguagens nem mesmo suportam herança múltipla! Outra, herança múltipla pode te trazer outros problemas seríssimos. “Ah, então eu escolho uma das duas pra herdar e o outro método eu copio. =]” Nossa! Eu não sei como eu penso nessas coisas… Mas é uma idéia terrível! Primeiro, você não deve herdar classes com o objetivo de economizar linhas de código! Isso escapa completamente o objetivo de OO. Você herda quando uma classe realmente têm um relacionamento “IS-A”. Ou seja,  quando ela for uma especialização da classe pai e esta for uma generalização das filhas. No caso, JogadorTenis não é um JogadorHoquei nem tampouco um JogadorFutebol. Esqueça economizar linhas de código.

Esse primeiro motivo deve bastar, mas eu ainda completo com o segundo. Você ainda assim estaria duplicando código de outras classes. Por exemplo, se você resolve criar uma classe JogadorTenis que ‘extends’ Jogador, e implementa o método correr() como escrito na classe JogadorFutebol e pular() como na classe JogadorHoquei você estaria duplicando código 2 vezes! E isso é ruim, muito ruim. Imagina se mais tarde é descoberto um erro no método da classe pai? “Copia e cola” seria o seu segundo nome… É… herança parece ser uma má idéia nesse caso aí. Outro problema: caso o jogador se machuque e não consiga mais, digamos, correr. Como fazer a instância de um jogador de futebol executar um correr() diferente dinamicamente? E agora? De fato, você ficaria sem saída. Você poderia fazer vários tipos de jogador de futebol representando os estados do seu JogadorFutebol, sendo que cada uma herdando a classe pai… não, essa não é uma solução elegante. A sua hierarquia iria aumentar, o que significa que a raiz do seu problema ia crescer e assim sucessivamente.  Imagina agora se surge um JogadorPoker. Mas enfim… a ideia está próxima! O que você acha de composição? Pense nos comportamentos que os jogadores podem ter relacionados com as suas ações. Por exemplo, correr como? Pular de que maneira? Correr rápido é uma forma de correr. Não correr poderia ser outra. Correr de patins seria outra… E pular? Pular alto como um jogador de vôlei seria uma forma. Pular baixo seria outra… já deu pra pegar a idéia. Então, podemos encapsular esses comportamentos visto que são eles que mudam (e não os jogadores como estávamos pensando no começo do problema!). Esses comportamentos serão uma hierarquia por si próprios. E a maldita da composição, onde entra na história? Os seus jogadores vão possuir(HAS-A) esses comportamentos. Mais explicitamente, no caso de correr:

public interface AcaoCorrer {
     public void correr();
}
public class CorrerComPatins implements AcaoCorrer {
     public void correr() {
         System.out.println("Correndo com patins...");
     }
}
public class CorrerRapido implements AcaoCorrer {
     public void correr() {
         System.out.println("Correndo rápido como um jogador de futebol...");
     }
}
public class NaoCorrer implements AcaoCorrer {   
     public void correr() {
         System.out.println("Não corre.");
     }
}

public abstract class Jogador {

     AcaoCorrer acaoCorrer;
     AcaoPular acaoPular;

     public Jogador() {
         this.acaoCorrer = new NaoCorrer();
         this.acaoPular = new NaoPular();
     }
     public void correr() {
         this.acaoCorrer.correr();
     }
     public void pular() {
         this.acaoPular.pular();
     }

     public abstract void exibir();
     public void setAcaoCorrer(AcaoCorrer acaoCorrer) {
         this.acaoCorrer = acaoCorrer;
     }
//Foi omitido, mas suponha que existe uma ampla variedade de formas de pular, sendo todas filhas de AcaoPular    
    public void setAcaoPular(AcaoPular acaoPular) {
         this.acaoPular = acaoPular;
    }
}


Funciona do mesmo jeitinho e com mais vantagens:

1 – Temos um design voltado a interfaces ao invés de classes. Favorece a flexibilidade. Quando fazemos um comportamento ser representado por uma interface, ao invés de ser implementado na classe Jogador ou em uma de suas subclasses, estamos de certa forma “liberando” esse comportamento pra fora da classe. O comportamento (ou a hierarquia de comportamentos) está fora da própria classe. Além disso, estamos nos referindo ao comportamento de forma generalista, no caso AcaoCorrer > CorrerComPatins por exemplo. E na implementação apenas mencionamos como atributo o tipo da interface AcaoCorrer e não as reais implementações.

2 – É menos complexo você compor uma classe com outra do que começar uma nova hierarquia. Você estará sujeito a menos erros bobos assim.  Ah sim! E hierarquias podem se tornar coisas do capeta depois de 3 níveis.

3 – Podemos mudar o comportamento dinamicamente ou “on the fly”. Com os métodos setter dos comportamentos, podemos mudar o tipo de comportamento que quisermos quando bem entendermos. Exemplo:


public class Jogo {

    public static void main(String[] args) {

//Um jogador de futebol corre muito e pula normalmente

        Jogador jogadorFutebol = new JogadorFutebol();

        jogadorFutebol.correr();

        jogadorFutebol.pular();

//Um jogador de hoquei corre de patins e não pula

         Jogador jogadorHoquei = new JogadorHoquei();

         jogadorHoquei.correr();

         jogadorHoquei.pular();

//Um jogador de hoquei machucado, não consegue correr nem pular

         jogadorHoquei.setAcaoCorrer(new NaoCorrer());

         jogadorHoquei.correr();

         jogadorHoquei.pular();

    }

}

Note que é possível setar os comportamentos de correr e pular porque o parâmetro esperado no método setter é uma generalização – uma interface. Qualquer classe que implemente a interface em questão (o que significa que esse comportamento saiba correr ou pular) poderá ser passada como novo comportamento do Jogador.  Caso surja um JogadorPoker, ele será composto por comportamentos já existentes, NaoCorrer e NaoPular. Logo, não haverá duplicação alguma de código.

Todos esses passos mostrados acima compõem o conhecido padrão de projeto Strategy. Esse padrão está catalogado como um dos 23 Design Patterns do “Gang of Four” ou “GoF”.  Da próxima vez que você se deparar com um problema que tenha essa natureza de encapsular vários algoritmos ou comportamentos num mesmo objeto, lembre-se do padrão Strategy, que pode “mudar as vísceras” de um objeto.  Calma! Não saia por aí dizendo que conheceu um jeito de modelar objetos que mudou a sua vida (principalmente se você descobriu isso aqui nesse blog, nesse caso pesquise mais um pouco =P ). Esta é uma solução para esse tipo de problema, NÃO PARA TODOS.  Por isso, use-a com equilíbrio.

Existem várias APIs famosas que usam Strategy, como JPA (que usa 3 algoritmos diferentes de mapear classes a tabelas do BD) e o componente JComponent (na escolha de Borders) do Swing.  Como eu falei, essa solução clássica resolve vários problemas, e se você for do tipo que se depara com muitos, Strategy vai ser algo comum na sua vida. Por isso, ao se deparar com um problema que parece ser facilmente resolvido estendendo classes e/ou criando uma nova hierarquia, pense se composição não seria uma melhor solução. Não apenas você ficará feliz com uma solução elegante e abrangente, mas os programadores que porventura pegarem esse código no futuro também ficarão. =]

Padrão