Construindo objetos complexos com Builders (parte 1 de 2)

Bem, pessoal?

No meu primeiro post sobre design patterns, dei uma breve introdução sobre o assunto, explicando o que são, para que servem, suas principais vantagens e riscos. Também exemplifiquei com o padrão Strategy, um dos mais populares. Recomendo a leitura desse primeiro post <<aqui>> antes de prosseguir.

Neste post falarei sobre o padrão Builder, dando um exemplo de sua utilização e preparando o terreno para o próximo post, que mostrará um exemplo utilizando o conceito de interface fluente.

O QUE É

Ao contrário do Strategy, que é um padrão comportamental, o padrão Builder está na categoria de padrões de criação, junto com Abstract Factory, Factory Method, Prototype e Singleton.

O padrão Builder tem por intenção, segundo o livro do GoF, “separar a construção de um objeto complexo da sua representação de modo que o mesmo processo de construção possa criar diferentes representações”.

Meio confuso? Vamos tentar traduzir: a responsabilidade de se criar um objeto é delegada para outro objeto (o Builder), que cria o objeto-produto parte por parte. Aliás, construir um objeto parte por parte é o foco do padrão e o que o difere dos outros padrões de criação citados acima.

Quando é dito “… possa criar diferentes representações”, é justamente pelo fato de podermos criar um objeto utilizando um de vários builders concretos, obtendo assim um produto diferente.

A estrutura do padrão Builder é ilustrada abaixo:

Padrão Builder

(Estrutura do padrão Builder. Fonte: DoFactory.com)

Onde:

Director utiliza a interface de Builder para construir um determinado objeto. Em outras palavras, criamos um Director e dizemos a ele qual Builder concreto iremos utilizar para construir o produto.

Builder especifica uma interface para criação das partes do produto. Pode ser implementado como interface ou como classe-abstrata. No diagrama, temos apenas “BuildPart”, mas na prática temos vários métodos, cada um criando uma parte do produto.

ConcreteBuilder implementa uma forma de construir o produto, parte por parte, seguindo a abstração Builder. ConcreteBuilder guarda a representação do objeto-produto e fornece um método para recuperá-lo, após sua construção (no diagrama, este método é o “GetResult”).

Product é o objeto-produto da construção “orquestrada” pelo Builder.

Vamos ver então como fica uma implementação simples do padrão.

EXEMPLO

Vamos começar definindo a interface para criação do produto. Chamamos este builder abstrato de CriadorDeGuerreiro e o produto será, obviamente, um Guerreiro, que será criado com espada, armadura e arco.

// Builder abstrato - fornece uma interface para criação de um guerreiro
public abstract class CriadorDeGuerreiro
{
    protected Guerreiro _guerreiro;
    public Guerreiro ObterGuerreiro()
    {
        return _guerreiro;
    }

    public abstract void ComEspada();
    public abstract void ComArmadura();
    public abstract void ComArco();
}

Aqui temos a primeira consideração do exemplo: o método para obtenção do produto está definido no builder abstrato, e, conforme vocês verão mais abaixo, o produto (Guerreiro) é uma classe abstrata.

Estou implementando desta forma para mostrar uma variação da estrutura “clássica” apresentada acima, onde cada builder concreto sabe como obter seu produto específico, isto é, cada builder concreto define seu método de obtenção do produto (GetResult, no diagrama). Em geral, quando os produtos criados são muito diferentes entre si e não teremos nada a ganhar com uma abstração, seguimos com a estrutura “clássica”.

Abaixo, segue a implementação do produto Guerreiro:

// Produto abstrato - resultado do builder
public abstract class Guerreiro
{
     public string Espada { get; protected set; }
     public string Armadura { get; protected set; }
     public string Arco { get; protected set; }

     public abstract void EscolherEspada(string espada);
     public abstract void ColocarArmadura(string armadura);
     public abstract void EscolherArco(string arco);
}

Algumas considerações sobre o produto abstrato:

– As partes que compõem o guerreiro (Espada, Armadura e Arco) são do tipo string apenas por simplicidade do exemplo.
– Os métodos que “setam” cada uma das armas do guerreiro são todos abstratos, mas poderíamos ter implementações-default que poderiam ou não serem sobrescritos nos produtos-concretos.

Em seguida, definimos um builder concreto, ou seja, aquele que realmente implementará a criação do produto, seguindo a interface do builder abstrato:

// Builder concreto - implementa uma forma de criar um guerreiro
public class CriadorDeGuerreiroMedieval : CriadorDeGuerreiro
{
    public CriadorDeGuerreiroMedieval()
    {
        _guerreiro = new GuerreiroMedieval();
    }

    public override void ComEspada()
    {
        _guerreiro.EscolherEspada("espada medieval");
    }

    public override void ComArmadura()
    {
        _guerreiro.ColocarArmadura("armadura medieval");
    }

    public override void ComArco()
    {
        _guerreiro.EscolherArco("arco medieval");
    }
}

Notem que o builder concreto sabe como criar um guerreiro específico, definindo suas armas no estilo medieval.

Abaixo segue a implementação do produto concreto (GuerreiroMedieval) criado pelo builder concreto:

// Produto concreto - criado pelo builder concreto
public class GuerreiroMedieval : Guerreiro
{
    public override void EscolherEspada(string espada)
    {
        Espada = espada;
    }

    public override void ColocarArmadura(string armadura)
    {
        Armadura = armadura;
    }

    public override void EscolherArco(string arco)
    {
        Arco = arco;
    }
}

De forma semelhante, podemos criar um novo builder concreto e um novo produto concreto:

// Builder concreto - implementa uma forma de criar um guerreiro
public class CriadorDeGuerreiroFuturista : CriadorDeGuerreiro
{
    public CriadorDeGuerreiroFuturista()
    {
        _guerreiro = new GuerreiroFuturista();
    }

    public override void ComEspada()
    {
        _guerreiro.EscolherEspada("espada futurista");
    }

    public override void ComArmadura()
    {
        _guerreiro.ColocarArmadura("armadura futurista");
    }

    public override void ComArco()
    {
        _guerreiro.EscolherArco("arco futurista");
    }
}
// Produto concreto - criado pelo builder concreto
public class GuerreiroFuturista : Guerreiro
{
    // (MESMA IMPLEMENTAÇÃO DO OUTRO PRODUTO CONCRETO "GuerreiroMedieval")
}

Por fim, implementamos o director, que damos o nome de Exercito:

// Director - faz uso de um builder para construir o produto
public class Exercito
{
    public void ConstruirGuerreiro(CriadorDeGuerreiro criadorDeGuerreiro)
    {
        criadorDeGuerreiro.ComArco();
        criadorDeGuerreiro.ComArmadura();
        criadorDeGuerreiro.ComEspada();
    }
}

Prontinho! Para vermos nossas classes em funcionamento, vamos demonstrar em uma aplicação console:

class Program
{
    static void Main(string[] args)
    {
        var exercito = new Exercito();
        CriadorDeGuerreiro criadorDeGuerreiro;
        Guerreiro guerreiro;

        // criando um guerreiro medieval
        criadorDeGuerreiro = new CriadorDeGuerreiroMedieval();
        exercito.ConstruirGuerreiro(criadorDeGuerreiro);
        guerreiro = criadorDeGuerreiro.ObterGuerreiro();
        Console.WriteLine("Guerreiro com as características: {0}, {1}, {2}",
                guerreiro.Arco, guerreiro.Armadura, guerreiro.Espada);

        // criando um guerreiro futurista
        criadorDeGuerreiro = new CriadorDeGuerreiroFuturista();
        exercito.ConstruirGuerreiro(criadorDeGuerreiro);
        guerreiro = criadorDeGuerreiro.ObterGuerreiro();
        Console.WriteLine("Guerreiro com as características: {0}, {1}, {2}",
                guerreiro.Arco, guerreiro.Armadura, guerreiro.Espada);
    }
}

CONCLUSÃO

Neste primeiro post sobre o design pattern Builder, mostrei o que é o padrão, sua intenção, sua estrutura e um exemplo. Este padrão pode ser utilizado sempre que (1) o processo de criação de um objeto for muito complexo; (2) você queira criar um objeto parte por parte; (3) você queira obter várias representações diferentes de um objeto.

No próximo post, mostrarei o mesmo exemplo, utilizando o conceito de interface fluente.

Até lá!

————————————————–
Referência:
Gamma, Erich. Helm, Richard. Johnson, Ralph. Vlissides, John. 1994. Design Patterns – Elements of reusable object-oriented software. Addison-Wesley Professional.

Anúncios

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s