Italo Info


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:

Diagrama do decorator - Desenho
Diagrama do decorator - Desenho

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:

Decoradores de desenhos - Bandeira do Brasil
Decoradores de desenhos - Bandeira do Brasil

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

Decoradores de desenhos - Bandeira do Japão
Decoradores de desenhos - Bandeira do Japão

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!