Padrão Decorator
Olá. Desta vez escrevo sobre um padrão de projeto para programação Orientada a Objetos muito conhecido: O Decorator. Esse padrão, quando aplicado ao projeto de software OO, visa a flexibilidade de adicionar ou remover comportamentos por encadear objetos de classes decoradoras.
Muito bem! Vamos ao exemplo de código. Veja o código abaixo:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BandeiraMain extends JPanel {
private final int LARGURA_RETANGULO = 300;
private final int ALTURA_RETANGULO = 250;
private final int LARGURA_LOSANGO = 280;
private final int ALTURA_LOSANGO = 230;
private final int RAIO_CIRCULO = 75;
@Override
public void paintComponent( Graphics g ) {
super.paintComponent( g );
int larguraTela = super.getWidth();
int alturaTela = super.getHeight();
int centroX = larguraTela / 2;
int centroY = alturaTela / 2;
// PINTA TELA DE PRETO
g.setColor( Color.BLACK );
g.fillRect( 0, 0, larguraTela, alturaTela );
// DESENHO DO RETANGULO
int x = centroX - ( LARGURA_RETANGULO / 2 );
int y = centroY - ( ALTURA_RETANGULO / 2 );
g.setColor( Color.GREEN );
g.fillRect( x, y, LARGURA_RETANGULO, ALTURA_RETANGULO );
// DESENHO DO LOSANGO
int x1 = centroX - ( LARGURA_LOSANGO / 2 );
int y1 = centroY;
int x2 = centroX;
int y2 = centroY - ( ALTURA_LOSANGO / 2 );
int x3 = centroX + ( LARGURA_LOSANGO / 2 );
int y3 = centroY;
int x4 = centroX;
int y4 = centroY + ( ALTURA_LOSANGO / 2 );
int[] xvetor = { x1, x2, x3, x4 };
int[] yvetor = { y1, y2, y3, y4 };
g.setColor( Color.YELLOW );
g.fillPolygon( xvetor, yvetor, 4 );
// DESENHO DO CIRCULO
int cx = centroX - RAIO_CIRCULO;
int cy = centroY - RAIO_CIRCULO;
int larg = RAIO_CIRCULO * 2;
int alt = RAIO_CIRCULO * 2;
g.setColor( Color.BLUE );
g.fillArc( cx, cy, larg, alt, 0, 360 );
}
public static void main( String[] args ) {
JPanel desenhoPainel = new BandeiraMain();
JFrame f = new JFrame();
f.setContentPane( desenhoPainel );
f.setTitle( "Desenhando com decorator pattern" );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setSize( 400, 400 );
f.setLocationRelativeTo( f );
f.setVisible( true );
}
}
Facilmente, percebemos no código acima que o conteúdo do método "paintComponent" está muito grande. E se tivéssemos que adicionar mais gráficos? Uma solução para melhorar o código acima é a utilização do padrão decorator. Então, vamos refatorar! Veja o diagrama abaixo que ilustra a solução com decorator:
Perceba que no diagrama acima há a interface DesenhoDecorator principal que fica no topo. Ela tem o método "desenha". Logo em seguida tem a classe base abstrata que é extendida pelas decoradoras. Ela implementa o método "desenha" da interface DesenhoDecorator. O restante das classes, são, também, classes concretas que implementam a classe base, implementando, também, seu método abstrato "desenhaGrafico". É nesse método que fica o comportamento, isto é, os desenhos, conforme o contexto da classe.
Agora, veja a seguir, a solução que reflete o refatoramento e implementação do padrão decorator ao exemplo anterior!
Abaixo a classe DesenhoDecorator:
import java.awt.Graphics;
public interface DesenhoDecorator {
public void desenha( Graphics g, int larguraTela, int alturaTela );
}
DesenhoDecorator é uma interface muito simples. Com apenas um método a ser implementado pela classe BaseDesenhoDecorator que está logo a seguir:
import java.awt.Graphics;
public abstract class BaseDesenhoDecorator implements DesenhoDecorator {
private DesenhoDecorator desenho = null;
public BaseDesenhoDecorator(DesenhoDecorator desenho) {
this.desenho = desenho;
}
public abstract void desenhaGrafico( Graphics g, int larguraTela, int alturaTela );
@Override
public void desenha( Graphics g, int larguraTela, int alturaTela ) {
if ( desenho != null )
desenho.desenha( g, larguraTela, alturaTela );
this.desenhaGrafico( g, larguraTela, alturaTela );
}
}
A implementação também é simples. Isto é: as classe derivada desta classe base devem chamar o construtor da classe base, passando (ou repassando), o decorador para ser setado no atributo decorator da classe base. Assim, quando o método "desenha" for chamado, se o decorador não for null, então o método "desenha" do decorador é chamado, antes da chamada à implementação do método "desenhaGrafico". Veja abaixo como fica uma das classes concretas: A classe RetanguloDesenho:
import java.awt.Color;
import java.awt.Graphics;
public class RetanguloDesenho extends BaseDesenhoDecorator {
private final int largura;
private final int altura;
private final Color cor;
public RetanguloDesenho( DesenhoDecorator desenho, int largura, int altura, Color cor ) {
super( desenho );
this.largura = largura;
this.altura = altura;
this.cor = cor;
}
@Override
public void desenhaGrafico( Graphics g, int larguraTela, int alturaTela ) {
int centroX = larguraTela / 2;
int centroY = alturaTela / 2;
int x = centroX - ( largura / 2 );
int y = centroY - ( altura / 2 );
g.setColor( cor );
g.fillRect( x, y, largura, altura );
}
}
Perceba que a classe DesenhaRetangulo apenas extende a classe base, passando para ela o decorador recebido como parâmetro do construtor. Então, o método "desenhaGrafico" é implementado conforme o comportamento que se deseje que seja o desenho do retangulo. Veja abaixo as demais classes "decoradoras":
import java.awt.Color;
import java.awt.Graphics;
public class LosangoDesenho extends BaseDesenhoDecorator {
private final int largura;
private final int altura;
private final Color cor;
public LosangoDesenho( DesenhoDecorator decorator, int largura, int altura, Color cor ) {
super( decorator );
this.largura = largura;
this.altura = altura;
this.cor = cor;
}
@Override
public void desenhaGrafico( Graphics g, int larguraTela, int alturaTela ) {
int centroX = larguraTela / 2;
int centroY = alturaTela / 2;
int x1 = centroX - ( largura / 2 );
int y1 = centroY;
int x2 = centroX;
int y2 = centroY - ( altura / 2 );
int x3 = centroX + ( largura / 2 );
int y3 = centroY;
int x4 = centroX;
int y4 = centroY + ( altura / 2 );
int[] xvetor = { x1, x2, x3, x4 };
int[] yvetor = { y1, y2, y3, y4 };
g.setColor( cor );
g.fillPolygon( xvetor, yvetor, 4 );
}
}
import java.awt.Color;
import java.awt.Graphics;
public class CirculoDesenho extends BaseDesenhoDecorator {
private final int raio;
private final Color cor;
public CirculoDesenho( DesenhoDecorator desenho, int raio, Color cor ) {
super( desenho );
this.raio = raio;
this.cor = cor;
}
@Override
public void desenhaGrafico(Graphics g, int larguraTela, int alturaTela) {
int centroX = larguraTela / 2;
int centroY = alturaTela / 2;
int x = centroX - raio;
int y = centroY - raio;
int larg = raio * 2;
int alt = raio * 2;
g.setColor( cor );
g.fillArc( x, y, larg, alt, 0, 360 );
}
}
Agora, por último, vamos a classe TelaDesenho. Ela é também concreta e não recebe nenhum decorador no construtor. Portanto, deve ser a primeira na cadeia de decoradores! Veja abaixo:
import java.awt.Color;
import java.awt.Graphics;
public class TelaDesenho extends BaseDesenhoDecorator {
public TelaDesenho() {
super( null );
}
@Override
public void desenhaGrafico( Graphics g, int larguraTela, int alturaTela ) {
g.setColor( Color.BLACK );
g.fillRect( 0, 0, larguraTela, alturaTela );
}
}
Perceba que na classe TelaDesenho o construtor não recebe argumentos e passa "null" para o construtor da classe base! O método "desenhaGrafico" apenas desenha um retangulo de cor preta que ocupa a tela inteira.
Agora vamos juntar tudo na classe principal. Veja abaixo:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BandeiraMain extends JPanel {
private final DesenhoDecorator desenho;
public BandeiraMain( DesenhoDecorator desenho ) {
this.desenho = desenho;
}
@Override
public void paintComponent( Graphics g ) {
super.paintComponent( g );
int w = super.getWidth();
int h = super.getHeight();
desenho.desenha( g, w, h );
}
public static void main( String[] args ) {
DesenhoDecorator decorator = new TelaDesenho();
decorator = new RetanguloDesenho( decorator, 300, 250, Color.GREEN );
decorator = new LosangoDesenho( decorator, 280, 230, Color.YELLOW );
decorator = new CirculoDesenho( decorator, 75, Color.BLUE );
JPanel desenhoPainel = new BandeiraMain( decorator );
JFrame f = new JFrame();
f.setContentPane( desenhoPainel );
f.setTitle( "Desenhando com decorator pattern" );
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setSize( 400, 400 );
f.setLocationRelativeTo( f );
f.setVisible( true );
}
}
Perceba que no método main está a instanciação dos decoradores. Esse código desenha um retângulo, um losango e um círculo, formando uma figura semelhante a bandeira do Brasil. Veja abaixo o resultado:
Agora veja o resultado com a pequena alteração abaixo no método "main":
DesenhoDecorator decorator = new TelaDesenho();
decorator = new RetanguloDesenho( decorator, 300, 250, Color.WHITE );
decorator = new CirculoDesenho( decorator, 75, Color.RED );
Abaixo o link de download do código fonte do projeto que desenha uma bandeira.
Link de download: decorator-exemplo.zip
Após baixar o projeto, você pode compilar se estiver com o JDK. Então, use a linha de comandos para entrar na pasta base do projeto descompactado e utilize o seguinte comando para compilar:
javac -d bin src/bandeira/desenho/*.java src/bandeira/*.java
Agora para rodar, basta utilizar o seguinte comando:
java -cp bin bandeira.BandeiraMain
Baixe, compile e rode o projeto e experimente adicionar novos decoradores, ou remover decoradores encadeados ou, mesmo, alterar a ordem das instâncias e teste novamente para ver que gráfico é gerado!
Finalizando...
Esse é o final de mais um artigo sobre padrões de projeto. Desta vez, o padrão decorator foi discutido aqui. Espero que tenham gostado e, até o próximo!