Design Patterns em Scala – Parte 2: Decorator


No post anterior dessa série de Design Patterns falamos um pouco sobre maneiras diferentes de se implementar o pattern Observer em Scala.

Agora vejamos como as Traits podem nos ajudar a “decorar” nossas classes em runtime, com o Pattern Decorator.

Decorator

Muitas vezes quando queremos adicionar um comportamento a alguma classe, pensamos logo em herança, só que herança nem sempre é o melhor caminho. Vejamos o famoso exemplo das Janelas (decoração, janelas… acho vou mudar de ramo.).

Suponha uma classe que desenha uma janela simples na tela.

class Janela {
  def renderiza() = {
    print("Desenhando janela") // Use a imaginação cara!
  }
}

Ótimo, só que em um sistema de janelas, podemos ter diversos tipos de janelas, cada uma com uma combinação de funcionalidades:

  • Janela com barra de título
  • Janela com scroll vertical e barra de título
  • Janela com scroll horizontal, barra de título e barra de status
  • etc etc etc

Já de cara podemos ver que é impraticável criar uma subclasse para cada tipo de janela.

Aí vem a sacada do Decorator: ao invés de herança vamos usar composição e compor, a partir de pedaços de funcionalidade, a janela que quisermos. Tudo em runtime.

Vamos ver como implementar em Java da maneira chata usual:

// Interface implementada pela classe e pelos seus decorators
public interface Janela {
  public void renderiza();
}

// Decorator tem que manter controle de quem ele está decorando
abstract class JanelaDecorator implements Janela {
  private Janela janela;

  public Janela getJanela() {
    return this.janela;
  }

  public JanelaDecorator(Janela janela) {
    this.janela = janela;
  }

  public abstract void renderiza();
}

class JanelaSimples implements Janela {
  public void renderiza() {
    System.out.print("Desenhando Janela");
  }
}

class ScrollBarDecorator extends JanelaDecorator {
  public ScrollBarDecorator(Janela janela) {
    super(janela);
  }

  public void renderiza() {
    this.getJanela().renderiza();
    System.out.print(" com ScrollBar");  // Decorator adiciona comportamento
  }
}

Então, quando você tiver um monte de decorators implementados, você pode começar a decorar suas janelinhas:

Janela j = new ScrollBarDecorator(new JanelaSimples());
j.renderiza();
// Desenhando Janela com Scrollbar

// Você pode decorar com quantos decorators quiser.
Janela j = new ScrollBarDecorator(new TitleBarDecorator(new StatusBarDecorator(new JanelaSimples())));

Muito legal, mas eu só mostrei isso para você poder comparar com Scala.

Em Scala podemos fazer isso usando Traits:

// Nossa Janela
class Janela {
  def renderiza = print("Desenhando Janela")
}

// Quando uma Trait estende uma classe, ela só poderá ser misturada a objetos daquela classe. E também, "super" irá se referir à classe a qual ela foi misturada, ou outra Trait que foi misturada na classe.
trait ScrollBar extends Janela {
  override def renderiza = {
    super.renderiza
    print(" com Scrollbar")
  }
}

// Vamos usar
val janela = new Janela with ScrollBar
janela.renderiza 

// Desenhando janela com Scrollbar

Só isso! Meus dedos agradecem a redução de linhas.

E também aqui você pode usar quantos decorators quiser, separando as Traits com a palavra-chave with:

// "super" sempre vai se referir ao elemento da esquerda. 
val janela = new Janela with ScrollBar with StatusBar with TitleBar with Resize with BolinhasVerdes

Nesse exemplo, quando invocamos o método renderiza() no objeto janela, o primeiro
método a ser chamado é o da Trait a extrema direita BolinhasVerdes. A medida em que invocamos o método em
super, estamos referenciando a Trait logo a esquerda, até chegar na classe Janela.

É assim que funciona a composição de Traits.

Por hoje é só pessoal, e no próximo capítulo sobre Design Pattern:

Pimp My Library

Anúncios