Design Patterns: como você está construindo objetos complexos?

Amanda Moraes
Let’s code Brasil
4 min readJun 14, 2020
Uma imagem com fundo preto e frase em branco dizendo “Think twice, code once”

Os prazos estão encurtando. Precisamos ser mais ágeis. E mesmo quando só precisamos adicionar um contexto diferente para uma funcionalidade parecida, a atuação no código continua demorada e a reutilização da solução não pode ser trabalhada da melhor forma possível. E aquele código difícil de entender pra quem está chegando? Pois é. Ás vezes até erros ocorrem, ou mesmo a performance é afetada.

No fim, a experiência nos faz perceber que esses mesmos problemas aparecem com frequência, em mais sistemas, com mais desenvolvedores e a importância de se utilizar melhores práticas acaba entrando em foco. Então já que esses problemas são recorrentes, porque não padronizamos as soluções e disseminamos à comunidade?

Nuvem de palavras que referem-se a padrões de projetos como por exemplo: Observer, Singleton, Adapter, Builder, etc.

Em 1995, um grupo de 4 pessoas conhecidos como Gang of Four (GOF): Rich Gamma, Richard Helm, Ralph Johnson e John Vlissides; fizeram isso. Eles publicaram o livro Design Patterns: Elements of Reusable Object para propor a comunidade padrões de projeto que podem contribuir no melhor uso da programação orientada a objetos. Isso aumenta a velocidade de desenvolvimento, torna a manutenção do código mais viável e modular.

Logo, o conhecimento desses padrões é importante para entender o que eles podem ajudar no sistema quando for o caso. E dentre os padrões existentes, vou abordar nesse artigo um que tem papel importante na criação de objetos complexos. Esse padrão é o Builder.

Então, vamos falar sobre o Builder!

GIF do desenho “Bob, the buider” com o personagem martelando a fim de realizar a construção desejada.

Uma classe pode permitir várias representações de objeto. E para construí-los nesse contexto, é comum definirmos um construtor com vários parâmetros de entrada, que muitas vezes uns são inúteis em algumas representações e precisam ser preenchidos com valores default.

Ainda para solucionar o problema anterior, acabamos definindo vários construtores para adaptarmos parâmetros diferentes, o que as vezes pode confundir o desenvolvedor sobre qual usar.

Com base nisso, surge o padrão Builder como padrão de criação. Cujo objetivo é…

…separar a construção de um objeto complexo de sua representação para que o mesmo processo de construção possa criar representações diferentes.

- Gang of Four

Além da criação de representações diferentes de um mesmo objeto, implementá-lo ajuda também na questão de manter a complexidade sob controle, pois classes com muitas tarefas são mais difíceis de entender dificultando na manutenção e evolução do sistema. A lógica assim da criação do objeto fica apartada, dedicando a responsabilidade a classes específicas, mantendo alta coesão e modularidade.

Normalmente, no padrão Builder temos os seguintes tipos de classes:

  • A interface ou classe abstrata builder;
  • A classe de implementação da interface builder para a criação das partes do objeto;
  • O director (diretor) que chama os métodos do builder para gerar o objeto final;
  • E o product (produto), que representa o objeto.

Segue o diagrama UML dessas classes conforme apresentado no livro da GoF:

Diagrama do Builder representando de forma visual a descrição de todas as classes anteriormente mencionadas conectadas.
Diagrama UML do Builder

A seguinte estrutura apresentada pelo refactoring.guru detalha mais essas classes e mostra como se dá na construção do objeto:

Estrutura com classes mencionadas, expondo detalhes de métodos que podem ser aplicados em cada assim como exemplo.
Estrutura Detalhada do Builder

Let’s Code!

O exemplo que vou abordar é um projeto console desenvolvido em C# para a construção de labirintos que varia conforme nível de dificuldade desejado. Para tanto, começamos com a classe abstrata do Builder (que poderia ser adotado também interface nesse caso):

Nela definimos todos os passos que serão implementados nas diferentes representações do Labirinto. Implementei então com base nessa classe, o Labirinto Padrão (classe LabirintoPadraoBuilder):

E o Labirinto Difícil (classe LabirintoDíficilBuilder):

Veja que essas classes podem ser facilmente modificadas para novas configurações desses labirintos. Além de ser possível adicionar um novo builder para outra representação futuramente.

Segue a classe Labirinto com os atributos definidos e um método para exibição desses valores:

Na classe Director foi definido um método de construção que passa pelas etapas de criação do builder desse objeto.

E para vermos funcionando, construí os dois labirintos no método main exibindo seus valores ao final:

Segue a saída no console:

Caso a saída seja diferente para as representações desse objeto, esse método pode ser implementado também no builder. Mas a ideia no geral é isolar a lógica de criação dos objetos das regras de negócio do sistema. E tornar a construção mais reutilizada também.

Há alguns exemplos atualmente na comunidade que não utilizam o Director, sendo chamados diretamente os métodos dos Builders para a construção desses objetos.

Esse encadeamento é possível pois cada método retorna a própria classe builder referida. Essa versão do Builder foi proposta por Joshua Bloch na segunda edição do livro Effective Java.

Bom, ficamos por aqui. Até a próxima!

Referências

Design Patterns: Elements of Reusable Object-Oriented Software. Por Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.

Refactoring Guru: Builder. https://refactoring.guru/design-patterns/builder.

Engenharia de Software Moderna: 6- Padrões de Projeto. https://engsoftmoderna.info/cap6.html

--

--

Amanda Moraes
Let’s code Brasil

Engenheira de software. Além de viajante e gamer. De resto vou descobrindo por aí!