quarta-feira, 26 de agosto de 2015

Virando um pedreiro de software - Padrão de projeto Builder

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

O tema de hoje será um pouco diferente dos temas que geralmente abordamos. Iremos focar mais na forma de se construir um software e menos na tecnologia empregada. Hoje falaremos de padrões de projeto.

Você já ouviu algo sobre padrões de projeto ou sabe o que são ? Se sim, pule esta parte do post caso contrário, é interessante que você gaste cinco minutinhos lendo esse trecho introdutório.

Já sabe como o padrão Builder funciona e quer dar uma olhada no projeto ? Clique aqui.

Os padrões de projeto nasceram com Christopher Alexander e ao contrário do que você pensa, Christopher não era da área de T.I. mas sim, da área de arquitetura civil. Nesse momento você pensa: Como um cara da área civil pode ter criado padrões que são utilizados em softwares ?

Vamos com calma !! 

Christopher notou que padrões podiam ser estabelecidos na área da construção civil então, ele publicou em 1977 um catálogo com mais de 250 padrões para a arquitetura civil, que discutiam questões comuns da arquitetura, descrevendo em detalhe o problema e as justificativas de sua solução.

O que isso trouxe de vantagem ?

Muita coisa visto que, se uma outra pessoa tivesse um problema o qual, já estivesse catalogado, ela poderia atingir facilmente atingir a solução ou, se o problema fosse parecido, ela poderia adaptar a solução.

Porém, é possível dizer que um problema e sua solução formam um padrão por si só ?

A resposta é não. Para podermos afirmar que um problema e sua solução formam um padrão, eles devem atender as seguintes características:

  • o problema
  • a solução
  • o contexto (ambiente)
  • regularidade
Para existir um padrão, um problema deve existir, obviamente. Se um problema existe, ele deve ter uma solução, o que é óbvio também. Tanto o problema quanto a solução estão em um determinado ambiente, ou seja, um contexto. A regularidade é a frequência que aquela solução se aplica ao problema em diversos contextos. Se tivermos um problema, com uma solução em um determinado contexto e ela (solução) não puder ser aplicada em outros contextos parecidos, não temos um padrão. Do que adianta a solução de um problema que só acontece em um determinado ambiente ?

A idéia de um padrão não é essa. Os padrões de projetos tornam mais fácil reutilizar soluções e arquiteturas bem sucedidas para construir estruturas de forma flexível e fácil de manter.

No âmbito do desenvolvimento de software não é diferente. Projetistas e arquitetos lidam diariamente com diversos problemas e através da observação dos mesmos, foram construídas formas de contornar ou solucionar esses problemas. Neste post, aprenderemos um desses diversos padrões criados. Como existem muitos problemas no desenvolvimento de software, os padrões foram divididos em categorias. A categoria que iremos abordar é a de criação. Nela são discutidas formas de criação de objetos.

E de repente uma wild pergunta appers: "Ué !?!?!? Formas de criação de objeto ? Mas para eu criar um objeto não basta só dar o new ? Porque eu preciso de um padrão só para isso !?"

Sua pergunta será respondida através dos diversos conceitos que iremos aprender a partir de agora.

A criação de objetos pode ser algo banal em sistemas pequenos ou caseiros mas, quando se está em um ambiente corporativo, com milhares de sistemas, repletos de regras de negócio pesadas, a lógica do negócio pode se misturar com a lógica de criação do objeto e o que era fácil torna-se preocupante ou, até mesmo difícil. O padrão Builder separa a construção de um objeto complexo ou produto, de sua representação de modo que o mesmo processo de construção possa criar diferentes representações.

No nosso exemplo, criaremos robôs logo, precisamos definir como será nosso objeto complexo. Exagerei na quantidade de atributos para tentar refletir ao máximo a realidade de um sistema pesado.

 public class Robo {  
      private Date dataDeFabricacao;  
      private String nome;  
      private String[] cores;  
      private String especialidadeDeCombate;  
      private String fonteDeAlimentacao;  
      private String tipoBlindagem;  
      private int quantidadeDeBracos;  
      private int quantidadeDeCabecas;  
      private int quantidadeDePernas;  
      private int quantidadeDeMisseis;  
      private int quantidadeDePilotos;  
      private int quantidadeDeCameras;  
      private int quantidadeDeCanhoes;  
      private boolean isAnalogico;  
      private boolean possuiCanhoPlasma;  
      private boolean possuiEspada;  
      private boolean possuiVoo;  
      private boolean possuiEsteira;  
      private boolean possuiEscudo;  
   
     /*gets e sets aqui*/  
 }  

Vamos definir os passos de construção dos nossos robôs, ou seja, o nosso Builder.

 public abstract class RoboBuilder {  
   
      protected Robo robo;  
        
      public RoboBuilder() {  
           this.robo = new Robo();  
      }  
        
      public Robo getRobo(){  
           return this.robo;  
      }  
        
      public abstract void buildCaracteristicasGerais();  
      public abstract void buildCabeca();  
      public abstract void buildTronco();  
      public abstract void buildBracos();  
      public abstract void buildPernas();  
      public abstract void buildSistemaDeArmas();  
      public abstract void buildSistemaDeVoo();  
      public abstract void buildSistemaDeDefesa();  
 }  

O nosso Builder é uma classe abstrata porque não definiremos aqui os comportamentos dos nossos robôs mas sim, quais passos devemos realizar para construir um robô. Se no Builder definimos só s passos então, onde iremos definir como cada robô funciona, ou seja, seu comportamento ? Isso será feito em classes concretas e lá iremos de fato, definir minunciosamente como cada robô opera.

Classe GipsyDangerBuilder


 public class GipsyDangerBuilder extends RoboBuilder {  
   
      @Override  
      public void buildCaracteristicasGerais() {  
           super.robo.setNome("GipsyDanger");  
           try {  
                super.robo.setDataDeFabricacao(new SimpleDateFormat("dd/MM/yyyy").parse("18/10/2025"));  
           } catch (ParseException e) {  
                e.printStackTrace();  
           }  
           super.robo.setCores(new String[]{"azul turquesa","branco"});  
      }  
        
      @Override  
      public void buildCabeca() {  
           super.robo.setQuantidadeDePilotos(2);  
           super.robo.setQuantidadeDeCabecas(1);  
           super.robo.setQuantidadeDeCameras(15);  
      }  
   
      @Override  
      public void buildTronco() {  
           super.robo.setAnalogico(true);  
           super.robo.setFonteDeAlimentacao("Nuclear");  
      }  
   
      @Override  
      public void buildBracos() {  
           super.robo.setQuantidadeDeBracos(2);  
      }  
   
      @Override  
      public void buildPernas() {  
           super.robo.setQuantidadeDePernas(2);  
           super.robo.setPossuiEsteira(false);  
      }  
   
      @Override  
      public void buildSistemaDeArmas() {  
           super.robo.setEspecialidadeDeCombate("Luta corpo a corpo");  
           super.robo.setQuantidadeDeMisseis(30);  
           super.robo.setQuantidadeDeCanhoes(25);  
           super.robo.setPossuiCanhoPlasma(true);  
           super.robo.setPossuiEspada(true);  
      }  
   
      @Override  
      public void buildSistemaDeVoo() {  
           super.robo.setPossuiVoo(false);  
      }  
   
      @Override  
      public void buildSistemaDeDefesa() {  
           super.robo.setTipoBlindagem("Aço");  
           super.robo.setPossuiEscudo(false);  
      }  
 }  

Classe ChernoAlphaBuilder


 public class ChernoAlphaBuilder extends RoboBuilder {  
        
      @Override  
      public void buildCaracteristicasGerais() {  
           super.robo.setNome("ChernoAlpha");  
           try {  
                super.robo.setDataDeFabricacao(new SimpleDateFormat("dd/MM/yyyy").parse("10/07/2020"));  
           } catch (ParseException e) {  
                e.printStackTrace();  
           }  
           super.robo.setCores(new String[]{"verde oliva"});  
      }  
        
      @Override  
      public void buildCabeca() {  
           super.robo.setQuantidadeDePilotos(2);  
           super.robo.setQuantidadeDeCabecas(1);  
           super.robo.setQuantidadeDeCameras(10);  
      }  
   
      @Override  
      public void buildTronco() {  
           super.robo.setAnalogico(true);  
           super.robo.setFonteDeAlimentacao("Energia solar");  
      }  
   
      @Override  
      public void buildBracos() {  
           super.robo.setQuantidadeDeBracos(2);  
      }  
   
      @Override  
      public void buildPernas() {  
           super.robo.setQuantidadeDePernas(2);  
           super.robo.setPossuiEsteira(false);  
      }  
   
      @Override  
      public void buildSistemaDeArmas() {  
           super.robo.setEspecialidadeDeCombate("Luta corpo a corpo e uso de artilharia pesada");  
           super.robo.setQuantidadeDeMisseis(50);  
           super.robo.setQuantidadeDeCanhoes(33);  
           super.robo.setPossuiCanhoPlasma(false);  
           super.robo.setPossuiEspada(false);  
      }  
   
      @Override  
      public void buildSistemaDeVoo() {  
           super.robo.setPossuiVoo(false);  
      }  
   
      @Override  
      public void buildSistemaDeDefesa() {  
           super.robo.setTipoBlindagem("Aço cromo com adição de carbono");  
           super.robo.setPossuiEscudo(false);  
      }  
 }  

Quem utilizam as representações do Builder para iniciar a construção dos nossos robôs é a classe Director.

 public class FabricaDirector {  
      protected RoboBuilder roboBuilder;  
        
      public FabricaDirector(RoboBuilder roboBuilder) {  
           this.roboBuilder = roboBuilder;  
      }  
        
      public void buildRobo(){  
           roboBuilder.buildCaracteristicasGerais();  
           roboBuilder.buildCabeca();  
           roboBuilder.buildTronco();  
           roboBuilder.buildBracos();  
           roboBuilder.buildSistemaDeArmas();  
           roboBuilder.buildSistemaDeDefesa();  
           roboBuilder.buildPernas();  
           roboBuilder.buildSistemaDeVoo();  
      }  
   
      public Robo getRobo() {  
           return roboBuilder.getRobo();  
      }  
 }  

Nossa classe Director tem um método que inicia a construção, um método que retorna o robô construído e um atributo do tipo RoboBuilder o qual, é inicializado pelo construtor.

Analisando melhor:
  • O parâmetro do construtor é necessário porque assim, conseguimos passar qualquer tipo de Builder para o nosso Director e consequentemente, trabalhar com vários tipos de robôs.
  • O que torna nossa classe Director completamente flexível e desacoplada, é o fato do nosso Builder ser abstrato e suas representações, concretas. Isto é, no Director lidamos diretamente com os métodos definidos no Builder abstrato logo, não me importa como foram definidos (implementados) porque, no fim desse processo de produção, eu sei que o que será gerado, é um robô.
A classe que utiliza nosso Director para receber os robôs construídos, é uma classe cliente qualquer. Vejamos abaixo:

 public class Resistencia {  
      public static void main(String[] args) {  
           FabricaDirector fabricaDirector = new FabricaDirector(new GipsyDangerBuilder());  
           fabricaDirector.buildRobo();  
           Robo gipsyDanger = fabricaDirector.getRobo();  
             
           System.out.println("Ao ataque !!!");  
           System.out.println("Nome: " + gipsyDanger.getNome() + " - Especialidade de combate: " + gipsyDanger.getEspecialidadeDeCombate());  
             
           fabricaDirector = new FabricaDirector(new ChernoAlphaBuilder());  
           fabricaDirector.buildRobo();  
           Robo chernoAlpha = fabricaDirector.getRobo();  
             
           System.out.println("Ao ataque !!!");  
           System.out.println("Nome: " + chernoAlpha.getNome() + " - Especialidade de combate: " + chernoAlpha.getEspecialidadeDeCombate());  
      }  
 }  

A classe Resistencia necessita produzir robôs para o "ataque" (se você viu o filme Pacific Rim, sabe do que eu to falando) e utiliza a classe Director para tal. Note que ela não tem o mínimo conhecimento de como os robôs são produzidos, a única coisa que ela precisa saber é o que pedir, ou seja, se ela precisa de um robô Cherno Alpha ou um Gipsy Danger (põe no google que você vai ver o que são). Ela solicita o desejado para a classe Director e a mesma inicia a construção do robô e devolve ao cliente o robô construído e pronto para funcionar.

Mais uma vez atingimos o desacoplamento e a flexibilidade. A imagem abaixo mostra como as classes do nosso exemplo se relacionam.
Figura 1 - Diagrama de classe
Para baixar clique aqui.

Agradecimentos a Leonardo Palmeiro pela sugestão de post.

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários ou na nossa página do facebook.

Facebook: https://www.facebook.com/precisoestudarsempre/

Referências:
  1. Mão na massa: Builder - https://brizeno.wordpress.com/2011/09/25/mao-na-massa-builder/
  2. Rocha, Helder. J930: GoF Design Patterns em Java
  3. Guerra, Eduardo. Design Patterns com Java - Projeto orientado a objetos guiado por padrões. Casa do código
  4. Leite, Alessandro FerreiraConheça os Padrões de Projeto. http://www.devmedia.com.br/conheca-os-padroes-de-projeto/957#ixzz3PerhRtcc

Nenhum comentário: