O desenvolvimento de software é uma das áreas que mais ocorrem problemas durante o seu processo. Porém, um grupo de profissionais identificou que alguns problemas ocorrem frequentemente e por isso resolveram criar um catálogo que os identificassem e como eles poderiam ser resolvidos da melhor forma possível.

Esses profissionais eram quatro: Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, e foram apelidados de Gang of Four (GoF) ou Gangue dos Quatro. Em 1995 eles lançaram o livro "Design Patterns - Elements of Reusable Object-Oriented Software", que rapidamente ficou reconhecido mundialmente e hoje é o livro de cabeceira de qualquer desenvolvedor de sistemas, que contém vinte e três padrões e atualmente diversas literaturas têm lançado mais padrões.

Os que são tratados no artigo se referem ao que o GoF chama de padrões criacionais: Singleton, Abstract Factory, Prototype, Factory Method e Builder.

Esses padrões são chamados assim porque eles cuidam do ciclo de vida dos objetos, como a sua criação. Assim, esses procuram lidar da melhor forma para criar instâncias dos objetos através do encapsulamento do conhecimento sobre as classes concretas ou abstraindo como as instâncias são criadas e montadas.

No restante desse artigo analisaremos os primeiros cinco padrões do GoF, mas voltado para plataforma Java, diferentemente do livro do GoF, que traz os exemplos voltados para a linguagem C++. Utilizaremos a IDE Eclipse para montá-los.

Singleton

O GoF define o padrão Singleton como uma classe que possui apenas uma única instância e fornece um ponto de acesso global a ela.

Assim, ele é definido como uma classe que deve ter apenas uma instância a ser utilizada sempre que necessário.

Um exemplo clássico de utilização do padrão é quando os projetistas definem que para um determinado sistema deve ser utilizado um único file system: isso é importante quando se deseja um gerenciamento centralizado de recursos.

No exemplo da Listagem 1 tem-se um construtor privado que não pode ser inicializado. Quando existe uma tentativa de criar uma instância da classe, primeiramente é verificado se já existe uma e, caso exista, retorna-se essa instância. Caso não exista, ela será criada e retornada.


package br.com.devmedia.singleton;
class CriaPoolDeRecursos
{
  private static CriaPoolDeRecursos _pool;
  //O construtor foi definido como sendo privado para evitar o uso do new
 
  private CriaPoolDeRecursos() { }
 
  public static CriaPoolDeRecursos getPoolDeRecursos()
  {
   // Lazy initialization
   if (_pool == null)
   { 
    _pool = new CriaPoolDeRecursos();
    System.out.println("Criado um pool de recursos para a aplicação.");
   }
   else
   {
     System.out.print("Um pool de recursos já foi criado para a aplicação.");
    }
    return _pool;
 }
}
    
class SingletonPatternEx
{
   public static void main(String[] args)
   {
     System.out.println("***Exemplo do Padrão Singleton***\n");
     System.out.println("Tentando criar um pool de recursos para a aplicação.");
     CriaPoolDeRecursos p1 = CriaPoolDeRecursos.getPoolDeRecursos();
     System.out.println("Tentando criar outro pool de recursos para a aplicação.");
     CriaPoolDeRecursos p2 = CriaPoolDeRecursos.getPoolDeRecursos();
 
     if (p1 == p2)
     {
        System.out.println("p1 e p2 são as mesmas instâncias.");
      }
 }
}
Listagem 1. Exemplo de implementação do padrão Singleton

Na Figura 1 temos a saída do código e na Figura 2 temos o package explorer do Eclipse.

Saída da implementação do padrão Singleton
Figura 1. Saída da implementação do padrão Singleton
Package Explorer no Eclipse para a implementação do Singleton
Figura 2. Package Explorer no Eclipse para a implementação do Singleton

No código da Listagem 1 tem-se o que se chama de uma lazy initialization ou uma inicialização tardia, pois a instância dessa classe Singleton não será criada até que o método getPoolDeRecursos() seja chamado.

O grande problema da implementação proposta é que ela não é thread safe, ou seja, pode existir uma situação em que duas ou mais threads sejam criadas ao mesmo tempo e consigam assim criar mais de uma instância da classe Singleton.

Existe mais de uma forma de criar Singletons que sejam thread safe: segue na Listagem 2 um exemplo utilizando synchronized.


public static synchronized CriaPoolDeRecursos getPoolDeRecursos()
{
  //Código que retorna a instância
}
Listagem 2. Exemplo de implementação do padrão Singleton utilizando synchronized

O código previne a criação de mais de uma instância, no entanto, tem-se problemas de desempenho devido ao uso do synchronized.

Outra forma é utilizar um método do tipo eager initialization, ao contrário do lazy initialization anterior, como mostra o exemplo da Listagem 3.


class CriaPoolDeRecursos
{
   //Early Initialization (ou Inicialização tardia)
   private static CriaPoolDeRecursos _pool = new CriaPoolDeRecursos();
 
   //O construtor foi definido como sendo privado para evitar o uso do new
   private CriaPoolDeRecursos() { }
 
   // Ponto de acesso global ao pool de recursos 
   public static CriaPoolDeRecursos getPoolDeRecursos()
   {
      return _pool;
   }
}
Listagem 3. Exemplo de implementação do padrão Singleton utilizando early initialization

Na solução apresentada, um objeto da classe Singleton é sempre instanciado. O problema do código é que, dependendo da situação, essa classe criada nunca precisaria ser utilizada.

O código da Listagem 4 é considerado o ideal, sendo uma abordagem diferente que as demais.


class CriaPoolDeRecursos
{
   private static CriaPoolDeRecursos _pool;
 
   private CriaPoolDeRecursos() { }
 
   private static class SingletonHelper {
      //Classe aninhada referenciada após a chamada de getPoolDeRecursos()
      private static final CriaPoolDeRecursos _pool = new CriaPoolDeRecursos();
   }
 
   public static CriaPoolDeRecursos getPoolDeRecursos()
   {
       return SingletonHelper._pool;
   }
}
Listagem 4. Exemplo de implementação do padrão Singleton utilizando classes aninhadas

Abstract Factory

O GoF define o Abstract Factory como um padrão que fornece uma interface para criação de famílias de objetos relacionados ou dependentes, sem especificar suas classes concretas.

Neste padrão tem-se um mecanismo de encapsulamento para agrupar fábricas individuais. Neste processo, uma interface é usada para criar objetos relacionados, assim não é possível chamar diretamente a classe concreta.

O benefício do padrão Abstract Factory é a possibilidade de trocar as implementações concretas sem alterar o código do usuário. O ponto negativo é que o debugging se torna bastante complexo.

O exemplo das Listagens 5 a 8 tem-se alguns filmes divididos por gêneros e as respectivas factorys que são responsáveis pela instanciação da família de produtos da mesma categoria.


package br.com.devmedia.abstractfactory;
public interface IFilmeAmericano {
       String nomeFilme();
}
 
package br.com.devmedia.abstractfactory;
public interface IFilmeBrasileiro {
       String nomeFilme();
}
 
package br.com.devmedia.abstractfactory;
public interface IFilmesFactory {
       IFilmeBrasileiro getFilmeBrasileiro();
       IFilmeAmericano  getFilmeAmericano();
}
Listagem 5. Interfaces para os produtos e para a factory

package br.com.devmedia.abstractfactory;
public class FilmeBrasileiroAcao implements IFilmeBrasileiro {
       @Override
       public String nomeFilme()
       {
             return "Cidade de Deus (2002)";
       }
}
 
package br.com.devmedia.abstractfactory;
public class FilmeBrasileiroComedia implements IFilmeBrasileiro {
       @Override
       public String nomeFilme()
       {
             return "Se eu Fosse Você (2014)";
       }
}
 
package br.com.devmedia.abstractfactory;
public class FilmeAmericanoAcao implements IFilmeAmericano {
       @Override
       public String nomeFilme()
       {
             return "Start Wars (2016)";
       }
}
 
package br.com.devmedia.abstractfactory;
public class FilmeAmericanoComedia implements IFilmeAmericano {
       @Override
       public String nomeFilme()
       {
             return "Everybody in Panic 2 (2014)";
       }
}
Listagem 6. Implementação dos produtos (filmes) que serão criados pela factory

package br.com.devmedia.abstractfactory;
public class FilmesAcaoFactory implements IFilmesFactory {
       
       public IFilmeBrasileiro getFilmeBrasileiro()
       {
             return new FilmeBrasileiroAcao();
       }
 
       public IFilmeAmericano getFilmeAmericano()
       {
             return new FilmeAmericanoAcao();
       }
       
}
 
package br.com.devmedia.abstractfactory;
public class FilmesComediaFactory implements IFilmesFactory {
 
       public IFilmeBrasileiro getFilmeBrasileiro()
       {
             return new FilmeBrasileiroComedia();
       }
 
       public IFilmeAmericano getFilmeAmericano()
       {
             return new FilmeAmericanoComedia();
       }
       
}
Listagem 7. Implementação das factorys que retornam os produtos

package br.com.devmedia.abstractfactory;
   
  public class AbstractFactoryPatternEx {
   
    public static void main(String[] args) {
   
      System.out.println("***Exemplo do padrão Abstract Factory***");
      FilmesAcaoFactory filmeAcao = new FilmesAcaoFactory();
      IFilmeBrasileiro filmeAcaoBR = filmeAcao.getFilmeBrasileiro();
      IFilmeAmericano filmeAcaoUS = filmeAcao.getFilmeAmericano();
   
      System.out.println("\nOs filmes de Ação catalogados são:");
      System.out.println(filmeAcaoBR.nomeFilme());
      System.out.println(filmeAcaoUS.nomeFilme());
  
      FilmesComediaFactory filmeComedia = new FilmesComediaFactory();
      IFilmeBrasileiro filmeComediaBR = filmeComedia.getFilmeBrasileiro();
      IFilmeAmericano filmeComediaUS = filmeComedia.getFilmeAmericano();
   
      System.out.println("\nOs filmes de Comédia catalogados são::");
      System.out.println(filmeComediaBR.nomeFilme());
      System.out.println(filmeComediaUS.nomeFilme());
  }
   
}
Listagem 8. Implementação do código principal que cria as factorys e os produtos

Segue na Figura 3 a saída do código apresentado e na Figura 4 o package explorer do Eclipse.

Saída da implementação do padrão Abstract Factory
Figura 3. Saída da implementação do padrão Abstract Factory
Package Explorer no Eclipse para a implementação do Abstract Factory
Figura 4. Package Explorer no Eclipse para a implementação do Abstract Factory

Este padrão é utilizado quando o sistema não precisa se preocupar sobre como os produtos devem ser criados ou compostos, quando queremos lidar com várias fábricas mais facilmente, separar classes concretas e fazer a troca de produtos mais facilmente.

Prototype

O GoF define o Prototype como um padrão que especifica os tipos de objetos a serem criados utilizando um protótipo. Ele fornece um método alternativo para instanciar novos objetos através da cópia ou clone de uma instância existente. A criação de uma nova instância é bastante custosa, assim esse padrão ajuda a resolver esse problema.

Um exemplo clássico do padrão Prototype é quando se tem um documento padrão: quando for preciso utilizá-lo novamente, ao invés de criá-lo novamente, basta realizar uma cópia dele com as alterações necessárias.

No exemplo das Listagens 9 a 12 tem-se um Prototype chamado Carro, com os Prototypes concretos Ford e Chevrolet, que necessitam implementar o método clone() definido em Carro. Pode-se verificar que o Carro é criado com um preço padrão, assim é preciso modificar o preço em cada um dos modelos. O cliente é o PrototypePatternEx.


package br.com.devmedia.prototype;
 
import java.util.Random;
 
public abstract class Carro implements Cloneable {
       public String nomeModelo;
       public int preco;
 
       public String getNomeModelo()
       {
             return nomeModelo;
       }
 
       public void setNomeModelo(String nomeModelo)
       {
             this.nomeModelo = nomeModelo;
       }
 
       public static int setPreco()
       {
             int preco = 0;
             Random r = new Random();
             int p = r.nextInt(100000);
             preco = p;
 
             return preco;
       }
 
       public Carro clone() throws CloneNotSupportedException
       {
             return (Carro)super.clone();
       }
}
Listagem 9. Implementação do Prototype Carro

package br.com.devmedia.prototype;
 
public class Ford extends Carro {
       public Ford(String m)
       {
             nomeModelo = m;
       }
 
       @Override
       public Carro clone() throws CloneNotSupportedException
       {
             return (Ford)super.clone();
       }
}
Listagem 10. Implementação concreta Ford do Prototype Carro

package br.com.devmedia.prototype;
 
public class Chevrolet extends Carro {
       public Chevrolet(String m)
       {
             nomeModelo = m;
       }
 
       @Override
       public Carro clone() throws CloneNotSupportedException
       {
             return (Chevrolet)super.clone();
       }
}
Listagem 11. Implementação concreta Chevrolet do Prototype Carro

package br.com.devmedia.prototype;
 
public class PrototypePatternEx  {
       
       
  public static void main(String args[]) throws CloneNotSupportedException {
             
    System.out.println("***Exemplo do padrão Prototype***\n");
    Carro chevrolet = new Chevrolet("Cruze Sport v6");
    chevrolet.preco = 100000;
    Carro ford = new Ford("Focus 2.0");
    ford.preco=500000;
 
    Carro bc1;
    //Clone do Objeto Chevrolet
    bc1 = chevrolet.clone();
 
    //Configurando preço que será maior que 100000
    bc1.preco = chevrolet.preco + Carro.setPreco();
    System.out.println("Nome do Carro: "+ bc1.getNomeModelo() + ", 
    Preço do Carro: " + bc1.preco);
 
    //Clone do Objeto Ford
    bc1 = ford.clone();

    //Configurando preço que será maior que 500000
    bc1.preco = ford.preco + Carro.setPreco();
    System.out.println("Nome do Carro: "+ bc1.getNomeModelo() + ", 
    Preço do Carro: "+bc1.preco);
             
 }
       
}
Listagem 12. Implementação do código principal que testa os Prototypes

Na Figura 5 temos a saída do código apresentado e na Figura 6 temos o package explorer do Eclipse.

Saída da implementação do padrão Prototype
Figura 5. Saída da implementação do padrão Prototype
Package Explorer no Eclipse para a implementação do Prototype
Figura 6. Package Explorer no Eclipse para a implementação do Prototype

Esse padrão é muito utilizado quando o sistema não precisa se preocupar com o mecanismo de criação dos produtos e quando é preciso instanciar classes em tempo de execução. O problema do Prototype é que sempre será necessário implementar o mecanismo de clone em cada subclasse.

Factory Method

O Factory Method é um padrão que define uma interface para criação de um objeto, mas permite que as subclasses decidam qual classe instanciar.

Um exemplo de utilização do Factory Method são as aplicações Windows, que possuem diferentes banco de dados como Oracle e SQL Server. Assim, sempre que for preciso inserir informações no banco de dados é preciso criar uma SqlConnection ou uma OracleConnection. Se isso for feito em um if-else teremos muito código e uma manutenção complicada.

Para resolver este problema basta utilizar o Factory Method, que possui uma estrutura básica com uma classe abstrata. As subclasses serão derivadas dessa classe e terão a responsabilidade do processo de instanciação. Repare nas Listagens 13 a 18.


package br.com.devmedia.factorymethod;
 
public interface IAnimal {
       void comunicar();
}
Listagem 13. Interface padrão para os produtos a serem criados

package br.com.devmedia.factorymethod;
 
public class Cachorro implements IAnimal {
       @Override
       public void comunicar()
       {
             System.out.println("Cachorro late!");
       } 
 
}
Listagem 14. Implementação do animal cachorro

package br.com.devmedia.factorymethod;
 
public class Gato implements IAnimal {
       @Override
       public void comunicar()
       {
             System.out.println("Gato mia!");
       }
}
Listagem 15. Implementação do animal gato

package br.com.devmedia.factorymethod;
 
public abstract class IAnimalFactory {
       public abstract IAnimal getTipoAnimal(String tipo) throws Exception;
}
Listagem 16. Implementação abstrata para criação do Factory Method concreto

package br.com.devmedia.factorymethod;

public class ConcreteFactory extends IAnimalFactory {
   @Override
   public IAnimal getTipoAnimal(String tipo) throws Exception
   {
         switch (tipo)
         {
                case "Gato":
                       return new Gato();
                case "Cachorro":
                       return new Cachorro();
                default:
                       throw new Exception( "O Tipo Animal 
                       "+tipo+" não pode ser instanciado.");
         }
   }
}
Listagem 17. Implementação concreta do Factory Method que cria os animais

package br.com.devmedia.factorymethod;

public class FactoryPatternEx {
 public static void main(String[] args) throws Exception
 {
   System.out.println("***Exemplo Padrão Factory:***\n");
   
   IAnimalFactory animalFactory = new ConcreteFactory();
   
   IAnimal gato = animalFactory.getTipoAnimal("Gato");
   gato.comunicar();
   IAnimal cachorro = animalFactory.getTipoAnimal("Cachorro");
   cachorro.comunicar();
   
   //Na tentativa de criar Pato será retornada uma exceção, 
   pois não existe um animal do tipo Pato.
   IAnimal pato = animalFactory.getTipoAnimal("Pato");
   pato.comunicar();
 }
}
Listagem 18. Implementação do código principal que testa o Factory Method

Na Figura 7 vemos a saída do código apresentado e na Figura 8 temos o package explorer do Eclipse

Saída da implementação do padrão Factory Method
Figura 7. Saída da implementação do padrão Factory Method
Package Explorer no Eclipse para a implementação do Factory Method
Figura 8. Package Explorer no Eclipse para a implementação do Factory Method

Este padrão é útil quando a responsabilidade da criação de objetos deve ser das subclasses e quando queremos que o sistema fique com menos acoplamento possível.

Builder

O padrão Builder separa a construção de um objeto complexo da sua representação, visto que o mesmo processo de construção pode criar diferentes representações. Ele é útil quando um algoritmo criacional de um objeto complexo é independente da montagem das partes do objeto.

O exemplo presente nas Listagens 19 a 24 possui o IBuilder, Carro, Moto, Produto e Director, onde Carro e Moto implementam a interface IBuilder, que por sua vez, é usada para criar partes do objeto Produto, que representa o objeto complexo que está em construção.

O processo de montagem é descrito em Produto na Listagem 22. A estrutura de dados Linked List em Produto foi utilizada para esta operação de montagem. Carro e Moto são implementações concretas que implementam a interface IBuilder, por isso o uso dos métodos constroiCarcaca(), inserePneus(), adicionaFarois() e getVeiculo(). Os três primeiros são utilizados para construir o corpo do veículo, inserir rodas e as luzes. Já o método getVeiculo() retorna o Produto, e por fim, Director é responsável por construir o veículo com a interface IBuilder.


package br.com.devmedia.builder;
 
public interface IBuilder {
       void constroiCarcaca();
       void inserePneus();
       void adicionaFarois();
       Produto getVeiculo();
}
Listagem 19. Interface para o Builder

package br.com.devmedia.builder;

public class Carro implements IBuilder {
  private Produto produto = new Produto();

  @Override
  public void constroiCarcaca()
  {
       produto.adicionar("Carcaça do Carro construída!");
  }

  @Override
  public void inserePneus()
  {
       produto.adicionar("4 rodas adicionadas!");
  }

  @Override
  public void adicionaFarois()
  {
       produto.adicionar("6 faróis inseridos!");
  }

  @Override
  public Produto getVeiculo()
  {
       return produto;
  }
}
Listagem 20. Implementação concreta do Carro para a interface Builder

package br.com.devmedia.builder;

public class Moto implements IBuilder {
   private Produto produto = new Produto();

   @Override
   public void constroiCarcaca()
   {
         produto.adicionar("Carcaça da Moto construída!");
   }

   @Override
   public void inserePneus()
   {
         produto.adicionar("2 rodas adicionadas!");
   }

   @Override
   public void adicionaFarois()
   {
         produto.adicionar("2 faróis inseridos!");
   }

   @Override
   public Produto getVeiculo()
   {
         return produto;
   }
}
Listagem 21. Implementação concreta da Moto para a interface Builder

package br.com.devmedia.builder;

import java.util.LinkedList;

public class Produto {

   private LinkedList<String> partesDoVeiculo;

   public Produto()
   {
         partesDoVeiculo= new LinkedList<String>();
   }

   public void adicionar(String parteDoVeiculo)
   {
         //Adiciona partes do veículo
         partesDoVeiculo.addLast(parteDoVeiculo);
   }

   public void exibir()
   {
         System.out.println("\n Produto completo: ");

         for(int i=0; i<partesDoVeiculo.size(); i++)
         {
                System.out.println(partesDoVeiculo.get(i));
         }
   }
}
Listagem 22. Classe que exibe informações e adiciona as partes do produto

package br.com.devmedia.builder;

public class Director {
   IBuilder meuBuilder;

   // Método com a série de passos para construção do veículo
   public void construir(IBuilder builder)
   {
         meuBuilder=builder;
         meuBuilder.constroiCarcaca();
         meuBuilder.inserePneus();
         meuBuilder.adicionaFarois();
   }
}
Listagem 23. Classe Director responsável pelo algoritmo padrão de construção do Builder

package br.com.devmedia.builder;

public class BuilderPatternEx {
   public static void main(String[] args)
   {
         System.out.println("***Exemplo do padrão Builder:***\n");
         Director director = new Director();
         IBuilder carroBuilder = new Carro();
         IBuilder motoBuilder = new Moto();

         // Construindo um Carro
         director.construir(carroBuilder);
         Produto p1 = carroBuilder.getVeiculo();
         p1.exibir();

         // Construindo uma Moto
         director.construir(motoBuilder);
         Produto p2 = motoBuilder.getVeiculo();
         p2.exibir();
   }
}
Listagem 24. Implementação do código principal que testa o Builder

A saída do código pode ser vista na Figura 9 e o package explorer do Eclipse na Figura 10.

Saída da implementação do padrão Builder
Figura 9. Saída da implementação do padrão Builder
Package Explorer no Eclipse para a implementação do Builder
Figura 10. Package Explorer no Eclipse para a implementação do Builder

Pode-se verificar que o processo complexo de construir está escondido, assim o foco está em como o produto será feito. Em geral, tem-se apenas um método que retornará o objeto complexo e os outros métodos serão responsáveis pelo processo de criação parcial.

Viu como utilizar os padrões é algo bem fácil! Isso porque eles nos ajudam a lidar com a complexidade do código.

Os demais padrões que o livro trata também são importantes, mas muito específicos. Com os padrões aqui aprendidos conseguimos otimizar completamente os nossos códigos.

Espero que tenham gostado e não deixe de utilizar.

Bibliografia:
  1. Erich Gamma, Ricard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
  2. Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.
  3. Deepak, A.; Dan, M.; John, C.; Core J2EE Patterns: Best Practices and Design Strategies (2nd Edition). Prentice Hall, 2003.