Design, Java, OO, Programming

Evitando duplicação com Strategy e Classes Anônimas

Suponha que temos a seguinte classe Desenho:

class Desenho {

	private final String nome;
	private final int decadaDeCriacao;

	public Desenho(String nome, int decadaDeCriacao) {
		this.nome = nome;
		this.decadaDeCriacao = decadaDeCriacao;
	}

	public String getNome() {
		return nome;
	}

	public int getDecadaDeCriacao() {
		return decadaDeCriacao;
	}
	
	public String toString() {
		return nome + " (" + decadaDeCriacao + ")";
	}
}

Com a classe Desenho em mãos, vamos criar uma classe principal e alguns desenhos, colocando-os em uma lista:

class Programa {
	public static void main(String[] args) {
		Desenho popeye = new Desenho("Popeye", 1920);
		Desenho picaPau = new Desenho("Pica-pau", 1940);
		Desenho flintstones = new Desenho("Flintstones", 1960);
		Desenho scoobyDoo = new Desenho("Scooby-Doo", 1970);
		Desenho simpsons = new Desenho("Simpsons", 1990);
                
		List<Desenho> desenhos = 
                Arrays.asList(popeye, picaPau, 
                            flintstones, scoobyDoo, simpsons);
	}
}

Essa lista de desenhos poderia, ao invés de ser criada na mão, vir de um banco de dados através de uma DAO.

Dentro da main, vamos filtrar todos os desenhos que foram criados antes de 1960 e imprimi-los:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getDecadaDeCriacao() < 1960){
		antesDe1960.add(desenho);
	}
}
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Deverá ser impresso:

Popeye (1920)
Pica-pau (1940)

E se quisermos filtrar todos os desenhos que começam com a letra “S”? Faríamos algo como:

List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getNome().startsWith("S")){
		comecamComS.add(desenho);
	}
}
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos a seguinte saída:

Scooby-Doo (1970)
Simpsons (1990)

Observem acima as linhas destacadas no código dos dois filtros. São as únicas linhas essenciais, com código que tem a ver com o que realmente queremos fazer: filtrar os desenhos.

O resto é código repetitivo, apenas dá suporte ao que queremos fazer. E esse código de suporte tem exatamente a mesma estrutura nos dois exemplos. Código duplicado, a raiz de todo o mal!

Evitando duplicação com Strategy e Classes Anônimas

Podemos evitar esse tipo de duplicação extraindo o código repetitivo para uma classe que filtra os desenhos:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(???){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Mas a comparação vai variar de acordo com o que quisermos filtrar. E agora?

Podemos utilizar um molde chamado Strategy, de maneira a isolarmos as diferentes comparações cada uma em uma classe própria, respeitando um contrato comum.

Generalizando: Strategy é a ideia de encapsular uma família de algoritmos intercambiáveis em objetos com um contrato comum.

Para definir um contrato em Java, utilizamos uma interface:

interface ComparacaoDeDesenhos {
	boolean valePara(Desenho desenho);
}

Devemos colocar a interface ComparacaoDeDesenhos como parâmetro do método filtra de FiltroDeDesenhos, usando-a para encapsular a lógica de filtragem dos desenhos de maneira polimórfica:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos, 
				ComparacaoDeDesenhos comparacao){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(comparacao.valePara(desenho)){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Para utilizarmos a nossa nova classe FiltroDeDesenhos para filtrar os desenhos criados antes de 1960, precisaríamos criar uma classe que atenda ao contrato definido pela interface ComparacaoDeDesenhos:

class ComparacaoDeDesenhosAntesDe1960 
				implements ComparacaoDeDesenhos {
	public boolean valePara(Desenho desenho) {
		return desenho.getDecadaDeCriacao() < 1960;
	}
} 

Então, poderíamos cria uma instância da nossa nova classe ComparacaoDeDesenhosAntesDe1960, e passá-la para o nosso FiltroDeDesenhos, depois imprimindo os desenhos filtrados:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

ComparacaoDeDesenhos comparacaoDeDesenhosAntesDe1960 = 
					new ComparacaoDeDesenhosAntesDe1960();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
					comparacaoDeDesenhosAntesDe1960);
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

A saída seria a mesma de antes:

Popeye (1920)
Pica-pau (1940)

Para filtrar os desenhos que começam com “S”, teríamos que criar a classe ComparacaoDeDesenhosQueComecamComS.

Mas será que, pra todo o tipo de comparação, temos que criar uma classe diferente? Se fosse assim, o número de classes ia ser imenso.

Ao invés de criar a classe ComparacaoDeDesenhosAntesDe1960, poderíamos criar uma instância de uma classe sem nome, que atende à interface ComparacaoDeDesenhos, tudo de uma vez:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

Continuaríamos com a mesma saída:

Popeye (1920)
Pica-pau (1940)

Observe demos um new na interface ComparacaoDeDesenhos, seguido de uma implementação do método valePara, sem fornecer um nome para a classe.

Ao criarmos uma implementação de uma interface que não tem nome, estamos criando uma classe anônima.

O trio “});” é bem familiar para quem usa Javascript, já que aparece nas funções de callback usadas por muitas bibliotecas.

Para filtrar todos os desenhos que começam com “S”, faríamos:

List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos exatamente a mesma saída de antes:

Scooby-Doo (1970)
Simpsons (1990)

Comparando as implementações

Se deixarmos a impressão dos desenhos filtrados de fora, teríamos o seguinte código antes do uso de Strategy com classes anônimas:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getDecadaDeCriacao() < 1960){
       		antesDe1960.add(desenho);
       	}
}
List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getNome().startsWith("S")){
       		comecamComS.add(desenho);
       	}
}

Já o código com Strategy e classes anônimas, ficou assim:

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});

O número de linhas ficou o mesmo, mas a modelagem da solução ficou melhor, evitando repetição do laço e do acúmulo dos desenhos filtrados em uma nova lista.

Uso de Strategy e Classes Anônimas em bibliotecas

É muito comum em bibliotecas do mundo Java utilizar o molde Strategy com classes anônimas.

Um exemplo bem comum é a interface Comparator da API de Collections do Java:

Collections.sort(desenhos, new Comparator<Desenho>() {
	public int compare(Desenho desenho1, Desenho desenho2) {
		return desenho1.getNome()
				.compareTo(desenho2.getNome());
	}
});
for (Desenho desenho : desenhos) {
	System.out.println(desenho);
}

O código acima, irá imprimir os desenhos ordenados por nome:

Flintstones (1960)
Pica-pau (1940)
Popeye (1920)
Scooby-Doo (1970)
Simpsons (1990)

Em frameworks como Spring, o uso de Strategy e classes anônimas é bastante comum. É usado, por exemplo, para gerenciar uma transação programaticamente:

transactionTemplate.execute(new TransactionCallback() {
	// código executado em uma transação
	public Object doInTransaction(TransactionStatus status) {
 		atualizarPedidos(compra.getPedidos());
		return atualizarTotalDaCompra(compra);
 	}
});

Concluindo

Através do uso de Strategy e classes anônimas, conseguimos criar uma solução que encapsula a estrutura, permitindo-nos focar na essência do que estamos fazendo e, dessa maneira, evitando duplicação.

Essa solução é bastante usada em bibliotecas e frameworks do mundo Java.

Porém, a sintaxe de classes anônimas em Java é assustadora.

No próximo post, veremos o que o Java 8 tem a oferecer para deixar nosso código mais sucinto.

O código desse post pode ser encontrado em: https://gist.github.com/alexandreaquiles/9644356

Anúncios
Design, Programming

Porque armazenar numéricos como String?

Uma dúvida comum para quem está começando é como armazenar coisas como CPF, RG, Telefone ou coisas do gênero. Devemos usar números ou texto?

São valores, à princípio, compostos por números. É natural pensar em armazenar como inteiro: int ou Integer no Java; INTEGER nos bancos de dados que atendem ao padrão ANSI).

Algumas pessoas pensam em valor com pontos flutuantes (float ou double) porque podem representar dígitos nas casas decimais.

Além disso, armazenar um número é muito mais eficiente (em termos de espaço) que armazenar um texto: no MySQL, um INT pode armazenar valores de até 9 dígitos em 4 bytes enquanto um VARCHAR teria que usar até 10 bytes.

Mas será que economizar bytes de armazenamento é tão importante em uma época que você pode levar uns 32 bilhões de bytes no bolso?

Armazenar RG, CPF ou Telefone como texto é vantajoso porque:

  • zeros à esquerda são importantes e números iriam removê-los;
  • valores negativos não fazem sentido (e não existem unsigned ints em Java);
  • não faremos cálculos ou contagens com esses valores;
  • o importante não são os números, mas os caracteres que, por acaso, são numéricos;
  • o RG, por exemplo, pode ter letras. É regra em Minas Gerais;
  • guardar a formatação original com, p. ex., parênteses e traços pode ser interessante.

Ao modelar a sua aplicação, é fundamental comparar as alternativas. Facilidade de manutenção, corretude, rapidez de desenvolvimento (não gosto de usar o termo produtividade) devem ser favorecidas em relação a performance crua.

Design, OO

Moldes de Modelagem

moldes de modelagem

Há algum tempo, o grande Akita blogou sobre o termo “Padrões de Projeto” e como esse termo leva a uma confusão de conceitos.

Essa confusão acontece porque as palavras “Padrão” e “Projeto” tem vários significados na língua portuguesa.

“Padrão” pode significar duas coisas:

“Projeto” também pode significar duas coisas:

Os design patterns do mundo do software, cuja ideia originou-se da arquitetura de prédios, são uma coleção de soluções comuns para problemas recorrentes. Não são normas de planos, nem moldes de planos. Também não são normas de modelagem.

Design patterns são moldes de modelagem.

Design, OO, Programming

Ouça seus imports

Gerenciar dependências é um dos aspectos mais importantes de um software tecnicamente bom. Baixo acoplamento é um dos principais objetivos de quem deseja um código mais fácil de ler, manter e reutilizar.

Com o passar do tempo, descobri que verificar os imports de uma classe Java é uma boa maneira de avaliar suas dependências.

Exemplo

Observe abaixo a seção de imports de uma classe Java, que pertence a um software comercial. Você consegue identificar algum problema nas dependências dessa classe?

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.io.FileTransfer;

Acima, podemos ver 5 grupos de dependências:

  • Com as bibliotecas do Java:  java.io.*, java.text.* e java.util.*
  • Com o Google Guice, um framework de injeção de dependências: com.google.inject.*
  • Com o JasperReports, um framework de relatórios:  net.sf.jasperreports.*
  • Com a API de Servlets: javax.servlet.*
  • Com o DWR, um framework de Ajax: org.directwebremoting.*

Classificando os imports, podemos descobrir mal-cheiros nas dependências e decidir se são problemas de fato.

Podemos, então, classificar as dependências em:

  • Infra-estrutura: bibliotecas do Java e Google Guice
  • Relatórios: JasperReports
  • Web: API de Servlets e DWR

Mesmo sem saber detalhadamente o contexto em que essa classe está sendo usada, o fato de uma classe depender do JasperReports e do DWR ao mesmo tempo levanta suspeitas de problemas nas dependências!

Na minha opinião, a classe acima tem um problema de muitas responsabilidades (ou seja, falta de coesão): é responsável por gerar um relatório mas, além disso, por buscar informações da Web.

Porém, sem saber mais detalhes sobre o código e onde a classe é utilizada, não podemos decidir se os imports suspeitos são de fato culpados.

Um pouco mais de contexto

Agora, olhe o pacote dessa classe:

package com.blablabla.reports;

Através do nome do pacote (reports), confirmamos que a classe é responsável por gerar relatórios.

Considerando o Princípio da Responsabilidade Única (SRP), essa classe só deveria depender de classes envolvidas na geração de relatórios.

Buscar informações da Web não deveria pertencer às atividades dessa classe. Essa classe deveria ser ignorante da existência da Web.

Ao invés disso, essas informações deveriam ser oferecidas por quem a utiliza através de parâmetros e/ou configurações.

Considerando modularização

Pode ser que uma classe geradora de relatórios depender da Web seja válido na arquitetura atual da aplicação.

Mas e se você quiser reutilizar essa relatório em uma outra aplicação ou em um software Desktop? Basta separar esse pacote de relatórios em um JAR separado e reutilizá-lo nesses outros softwares.

Seu módulo de relatórios deveria depender dos jars do JasperReports e do modelo de domínio da sua aplicação. Até aí, tudo bem.

Mas, além desses, também teria que depender dos jars da API de Servlets e do DWR. Mal cheiro no ar!

E conseguimos identificar esse mal cheiro apenas batendo o olho nos imports.

Na literatura

No excelente livro Growing Object-Oriented Software, Guided by Tests, os autores mencionam a análise dos imports no capítulo 17 (Teasing Main Apart).

In its current form, Main acts as a matchmaker but it’s also implementing some of the components, which means it has too many responsibilities. One clue is to look at its imports:

 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.awt.util.ArrayList;
 import javax.swing.SwingUtilities;
 import org.jivesoftware.smack.Chat;
 import org.jivesoftware.smack.XMPPConnection;
 import org.jivesoftware.smack.XMPPException;
 import auctionsniper.ui.MainWindow;
 import auctionsniper.ui.SnipersTableModel;
 import auctionsniper.AuctionMessageTranslator;
 import auctionsniper.XMPPAuction;

We’re importing code from three unrelated packages, plus the auctionsniper package itself.

O código acima tem 3 grupos de dependências:

  • Com as bibliotecas de UI do Java AWT e Swing: java.awt.* e javax.swing.*
  • Com o Smack, que implementa o protocolo XMPP:  org.jivesoftware.smack.*
  • Com o Auction Sniper, a aplicação que está sendo desenvolvida: auctionsniper.*

No resto do capítulo, os autores refatoram o código através de passos de bebê, com o objetivo de resolver os problemas de dependências indevidas.

Concluindo

Prestar atenção aos imports nos oferece pistas de problemas no gerenciamento de dependências do nosso código.