Desenvolvimento

14 dez, 2018

Arquitetura e desenvolvimento de software – Parte 04: Builder

100 visualizações
Publicidade

E ai, pessoal! Tudo certo?

Continuando nossa série de artigos sobre os 23 Design Patterns do GoF, vamos abordar nesta parte, o pattern Builder. Sem mais delongas, vamos a ele!

Builder

O Builder faz parte dos padrões criacionais, onde o mesmo tem como fundamento a separação da construção de um objeto complexo da sua representação, de forma que o mesmo processo de construção possa criar diferentes representações, baseado em regras e parâmetros que sejam informados ao objeto responsável pela construção.

Abaixo temos um diagrama UML representando esse padrão e seus elementos, conforme descrito no livro do GoF:

  • Director: constrói um objeto utilizando a interface do builder
  • Builder: especifica uma interface para um construtor de partes do objeto-produto
  • ConcreteBuilder: define uma implementação da interface builder, mantém a representação que cria e fornece interface para recuperação do produto
  • Product: o objeto complexo construído. Inclui classes que definem as partes constituintes

O padrão Builder pode ser utilizado em uma aplicação que converte o formato de texto X para uma série de outros formatos e que permite a inclusão de suporte para conversão para outros formatos, sem a alteração do código fonte do leitor de X.

A solução para esse problema pode ser realizada através da implementação de uma classe de leitura (director), associada a uma classe capaz de converter o formato do arquivo para outra representação (builder).

O objeto da classe de leitura lê cada bloco do texto e executa o método apropriado no objeto de conversão, de acordo com tipo do bloco.

A classe de conversão possui um método para cada tipo de bloco, incluindo os caracteres comuns, parágrafos, fontes e etc. Para cada formato de texto suportado é criada uma classe de conversão especializada (concrete builder).

Um conversor para o formato PDF, por exemplo, processaria qualquer requisição para poder converter o estilo, além do texto, algo que um conversor para TXT poderia ignorar seções como imagens ou caracteres não compatíveis com a codificação do arquivo destino.

Ficha resumo

Vamos à ficha resumo do pattern, onde daremos um exemplo em C# e teremos suas vantagens e desvantagens:

  • Nome: Builder;
  • Objetivo/intenção: separar a lógica de construção de nossos objetos. Toda essa complexidade, dos mesmos, permitindo que esse processo de construção possa criar diferentes implementações;
  • Motivação: a criação do objeto talvez seja complexa e precise de uma quantidade de código significativa, deixando a representação do mesmo um pouco extensa e difícil de manter. Com isso, criamos outro tipo de objeto, responsável por construir nossos objetos, isolando essa complexidade de sua representação final, e sempre que possível, programando para interfaces, tanto para o builder como para o objeto a ser criado.
  • Aplicabilidade: esse padrão é muito utilizado quando desejamos, além de isolar a complexidade de criação de nossos objetos, criar diferentes implementações, baseadas em uma interface comum. Isso pode ser feito de diversas maneiras, seja possuindo diversas implementações de uma interface de builder ou até mesmo recebendo parâmetros na construção deste builder, que serão utilizados no método build(), geralmente usado para criar nossos objetos finais.
  • Estrutura: abaixo temos um exemplo de estrutura onde temos uma classe mais a ponta, a Director, que é responsável por chamar o método de construção do Builder – este que é uma interface, com suas implementações especializadas, como no caso o ConcreteBuilder, que possui em sua implementação a lógica para construir nossa classe final, a Product;

  • Consequências: conseguimos variar a representação interna de um produto, encapsular e separar o código da construção e da representação de objetos e maior controle no processo de construção, mas em contrapartida, temos que instanciar um ConcreteBuilder específico para cada tipo de objeto que queremos produzir, seja através de diferentes implementações ou de seus argumentos de instanciação.
  • Implementações: temos abaixo um código em C# que demonstra a implementação do pattern para o exemplo da criação de objetos através de builders específicos:
using System;
using System.Collections.Generic;
 
namespace DoFactory.GangOfFour.Builder.Structural
{
  public class MainApp
  {
    public static void Main()
    {
      Director director = new Director();
 
      Builder b1 = new ConcreteBuilder1();
      Builder b2 = new ConcreteBuilder2();
 
      director.Construct(b1);
      Product p1 = b1.GetResult();
      p1.Show();
 
      director.Construct(b2);
      Product p2 = b2.GetResult();
      p2.Show();
 
      Console.ReadKey();
    }
  }
 
  class Director
  {
    public void Construct(Builder builder)
    {
      builder.BuildPartA();
      builder.BuildPartB();
    }
  }
 
  abstract class Builder
  {
    public abstract void BuildPartA();
    public abstract void BuildPartB();
    public abstract Product GetResult();
  }
 
  class ConcreteBuilder1 : Builder
  {
    private Product _product = new Product();
 
    public override void BuildPartA()
    {
      _product.Add("PartA");
    }
 
    public override void BuildPartB()
    {
      _product.Add("PartB");
    }
 
    public override Product GetResult()
    {
      return _product;
    }
  }
 
  class ConcreteBuilder2 : Builder
  {
    private Product _product = new Product();
 
    public override void BuildPartA()
    {
      _product.Add("PartX");
    }
 
    public override void BuildPartB()
    {
      _product.Add("PartY");
    }
 
    public override Product GetResult()
    {
      return _product;
    }
  }
 
  class Product
  {
    private List<string> _parts = new List<string>();
 
    public void Add(string part)
    {
      _parts.Add(part);
    }
 
    public void Show()
    {
      Console.WriteLine("\nProduct Parts -------");
      foreach (string part in _parts)
        Console.WriteLine(part);
    }
  }
}

Usos conhecidos: o padrão Builder é muitas vezes comparado ao padrão Abstract Factory, pois ambos podem ser utilizados para a construção de objetos complexos.

A principal diferença entre eles é que o Builder constrói objetos complexos passo a passo e procura evitar ser um anti-pattern, enquanto o Abstract Factory constrói famílias de objetos, simples ou complexos, de uma só vez.

Padrões relacionados: Abstract Factory, Factory Method, Prototype e Singleton.

Concluindo

Este foi o padrão Builder. Ele facilita bastante na separação de toda a lógica de criação de nossos objetos, evitando que as classes que representam esses objetos finais fiquem muito extensas e difíceis de manter, além de facilitar a implementação de mais de um builder especializado, novamente desenvolvendo para interfaces, onde podemos ter objetos especializados sendo construídos em cima de uma interface.

Na próxima parte desta série vamos abordar o padrão Prototype.

Como sempre, aguardo feedbacks e dúvidas de vocês! Nos vemos em breve!