O padrão de projeto Factory é um dos principais padrões de projeto e mais utilizados nas linguagens de programação mais atuais. Esse é bastante utilizado também por frameworks, como Spring. O padrão Factory tem duas variações: Factory Method e Abstract Factory e a intenção desses é fornecer uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.

No restante do artigo veremos como introduzir ambas as variações e exemplos de como implementar os dois padrões como POJO. Também será visto como o padrão Factory pode ser implementado no Java SE e na plataforma Java EE, além das vantagens de utilizar a injeção de dependência.

Confira também o curso de Loja virtual com Java EE e também todos os cursos de Java da DevMedia.

Padrão Factory

A proposta do padrão Factory é de criar objetos, por isso este é considerado um padrão criacional. Basicamente, a lógica criacional é encapsulada dentro do factory e, ou é fornecido um método que retorna um novo objeto criado (Padrão Factory Method) ou delega-se a criação do objeto para uma subclasse (Padrão Abstract Factory).

O cliente não precisa conhecer as diferentes implementações da interface ou da classe. A única coisa que o cliente precisa conhecer é a Factory (Factory Method ou Abstract Factory) para que possa obter uma instância de uma das implementações da interface. Dessa forma, os clientes são desacoplados da criação de objetos. Esse desacoplamento se dá em função da aplicação do princípio da inversão de dependência. Entre as vantagens do desacoplamento tem-se a possibilidade de serem implementadas classes concretas que podem ser alteradas sem afetar o cliente, reduzindo assim o acoplamento entre as classes e aumentando a flexibilidade.

Portanto, o padrão Factory possibilita desacoplar objetos de criação do sistema subjacente através do encapsulamento do código responsável pela criação de objetos. Isso tem como resultado também uma maior simplificação para os desenvolvedores quando é necessária a realização de refatoração no código, pois agora existe um único ponto onde as alterações podem ocorrer.

Frequentemente o Factory é implementado como um Singleton ou uma classe estática, pois normalmente apenas uma única instância é necessária. Com isso, tem-se uma centralização na criação do objeto Factory, o que permite uma maior organização e manutenibilidade do código-fonte, além da redução dos erros quando alterações ou atualização são realizadas.

Na plataforma Java EE, é utilizada a injeção de dependência através da anotação @Inject em combinação com @Producers o que torna a implementação relativamente mais simples.

No restante do artigo será visto o que é cada um dos padrões e como eles podem ser implementados tanto como POJOs quanto na plataforma Java EE.

Padrão Factory Method

O livro do GoF descreve o padrão Factory Method da seguinte forma: "O padrão Factory Method define uma interface para criar um objeto, mas permite que as subclasses decidam qual classe instanciar". Essas fábricas de criação minimizam o uso da palavra-chave "new", encapsulam o processo de inicialização e as diferentes implementações concretas. Além disso, essa centralização minimiza o efeito de adicionar e remover classes concretas no sistema e os efeitos das dependências de classes concretas.

Segue na Figura 1 o diagrama de classe do Factory Method.

Diagrama de classe do padrão Factory
Method

Figura 1. Diagrama de classe do padrão Factory Method.

Implementando um Factory Method em Código Puro (POJO)

O Factory Method não é muito complicado de ser implementado. No código das Listagens 1 a 5 tem-se a implementação do Factory usando uma MaquinaDeBebidas que distribui diferentes tipos de bebidas dependendo da implementação das suas subclasses.

Listagem 1. Implementação da classe abstrata MaquinaDeBebidas.

  public abstract class MaquinaDeBebidas {
   
           public abstract Bebida entregaBebiba();
   
           public String exibeMensagem() {
                     return "Bem-vindo à máquina de bebidas";
           }
  }

Listagem 2. Implementação da classe concreta MaquinaDeCafe.

  public class MaquinaDeCafe extends MaquinaDeBebidas {
           public Bebida entregaBebiba() {
                     return new Cafe();
           }
  }

Listagem 3. Implementação da classe concreta SoftDrinksMachine.

  public class MaquinaDeRefrigerante extends MaquinaDeBebidas {
           public Bebida entregaBebiba() {
                     return new Refrigerante();
           }
  }

Listagem 4. Implementação da classe Refrigerante implementando a interface Bebida.

  public interface Bebida {
  }
   
  public class Refrigerante implements Bebida {
           Refrigerante() {
                     System.out.println("Refrigerante");
           }
  }

Listagem 5. Implementação da classe Café implementando a interface Bebida.

  public class Cafe implements Bebida {
           Cafe() {
                     System.out.println("Café");
           }
  }

A implementação anterior mostra como as subclasses da classe abstrata MaquinaDeBebidas determina a bebida que será entregue. Isto permite que a classe MaquinaDeBebidas entregue qualquer objeto do tipo Bebida. Cada subclasse da classe abstrata MaquinaDeBebidas determina qual bebida será entregue.

O mais interessante seria a possibilidade de receber uma determinada bebida e escolher o tipo de bebida a ser retornada de acordo com a bebida. O código abaixo mostra como o método entregaBebiba() recebe um nome de uma bebida e então constrói e retorna o objeto bebida requisitado. O exemplo da Listagem 6 poderia ser utilizado pela máquina de café (MaquinaDeCafe), por exemplo.

Listagem 6. Método entregaBebiba entregando uma determinada bebida definida no enum.

  public enum TipoDoCafe {EXPRESSO, CARIOCA}
   
  public Bebida entregaBebiba(TipoDoCafe tipoDoCafe) {
  Bebida cafe = null;
   
  switch (tipoDoCafe) {
  case EXPRESSO: cafe = new CafeExpresso();
  case CARIOCA: cafe = new CafeCarioca();
  }
   
  return cafe;
   
  } 

Padrão Abstract Factory

O padrão Factory Method é bastante prático e direto de implementar, mas em sistemas mais complexos é necessário organizá-los. O padrão Abstract Factory é descrito no GoF como: "O Abstract Factory é um padrão que fornece uma interface para criar famílias de objetos dependentes ou relacionados sem especificar suas classes concretas".

Assim, o Abstract Factory oferece o encapsulamento de um grupo de fábricas e controla como o cliente acessará essas fábricas.

Segue na Figura 2 o diagrama de classes do Abstract Factory.

Diagrama de classes do Abstract Factory

Figura 2. Diagrama de classes do Abstract Factory.

Implementando um Abstract Factory em Código Puro (POJO)

Para demonstrar o funcionamento do Abstract Factory é visto um exemplo na Listagem 7, onde tem-se a definição de um produto que efetua duas operações.

Listagem 7. Definição de um produto abstrato com as suas respectivas classes concretas.

  abstract class AbstractProdutoA {
           public abstract void operacaoA1();
           public abstract void operacaoA2();
  }
   
  class ProdutoA1 extends AbstractProdutoA {
   
           ProdutoA1(String arg) {
                     System.out.println("Olá "+arg);
           } 
   
           public void operacaoA1() { 
                     //código aqui
           };
   
           public void operacaoA2() { 
                     //código aqui
           };
  }
   
  class ProdutoA2 extends AbstractProdutoA {
   
           ProdutoA2(String arg) {
                     System.out.println("Olá "+arg);
           } 
   
           public void operacaoA1() { 
                     //código aqui
           };
   
           public void operacaoA2() { 
                     //código aqui
           };
  }

Na Listagem 8 tem-se a definição de um segundo produto sendo eles B1 e B2.

Listagem 8. Definição de um segundo produto abstrato com as suas respectivas classes concretas.

  abstract class AbstractProdutoB {
           public abstract void operacaoB1();
           public abstract void operacaoB2();
  }
   
  class ProdutoB1 extends AbstractProdutoB {
           ProdutoB1(String arg) {
                     System.out.println("Olá "+arg);
           }
  }
   
  class ProdutoB2 extends AbstractProdutoB {
           ProdutoB2(String arg) {
                     System.out.println("Olá "+arg);
           }
  }

Já no código da Listagem 9 está a implementação do Abstract Factory para os dois produtos. A classe AbstractFactory declara uma única interface para criação de produtos.

Listagem 9. Definição da Abstract Factory.

  abstract class AbstractFactory {
           abstract AbstractProdutoA createProdutoA();
           abstract AbstractProdutoB createProdutoB();
  }

A criação é tarefa das classes ConcreteFactory, onde uma boa prática é a aplicação do padrão de projeto Factory Method para cada produto da família. Segue na Listagem 10 a implementação das fábricas concretas para a criação dos produtos do tipo 1 ou do tipo 2.

Listagem 10. Definição das Abstract Factory concretas.

  class ConcreteFactory1 extends AbstractFactory {
   
           AbstractProdutoA createProdutoA() {
                     return new ProdutoA1("ProdutoA1");
           }
   
           AbstractProdutoB createProdutoB() {
                     return new ProdutoB1("ProdutoB1");
           }
   
  }
   
  class ConcreteFactory2 extends AbstractFactory {
   
           AbstractProdutoA createProdutoA() {
                     return new ProdutoA2("ProdutoA2");
           }
   
           AbstractProdutoB createProdutoB() {
                     return new ProdutoB2("ProdutoB2");
           }
   
  }

Para que o cliente tenha um trabalho mais tranquilo na criação das Factory pode ser criado o código da Listagem 11 para facilitar.

Listagem 11. Exemplo de uma forma indireta de criar uma factory.

  class CriaFactory {
   
           private static AbstractFactory abstFactory = null;
   
           static AbstractFactory getFactory(String tipofactory){
                     if(tipofactory.equals("a")){
                              abstFactory = new ConcreteFactory1();
                     }else if(tipofactory.equals("b")){
                                        abstFactory = new ConcreteFactory2();
                              } return abstFactory;
           }
  }

O exemplo da Listagem 12 mostra como o cliente cria um Produto A.

Listagem 12. Exemplo de uso da Factory por um cliente.

  public class Cliente {
           public static void main(String args[]){
                     AbstractFactory abstfact = CriaFactory.getFactory("a");
                     AbstractProdutoA prodA = abstfact.createProdutoA();
           }
  }

Pode-se notar que a Factory amarra a criação de produtos relacionados, não permitindo a criação de produtos que não seja daquela família. Isso é muito utilizado nas interfaces gráficas, onde é permitido a criação de objetos que estejam relacionados com aquele tipo de interface.

Implementando Factory na plataforma Java EE

O Java EE oferece uma forma simples e elegante de implementar o padrão factory através de anotações e injeção de dependência. Na plataforma Java EE é utilizada a anotação @Produces para criar um objeto, e a anotação @Inject para injetar o objeto criado (ou recurso) onde isto é necessário. Segue na Listagem 13 um exemplo de implementação.

Listagem 13. Exemplo de implementação do Factory Method usando @Producer da plataforma Java EE.

  package br.com.devmedia.producer;
  import javax.enterprise.inject.Produces;
   
  public class EventProducer {
   
           @Produces
           public String getMensagem(){
                     return "Exemplo de um producer";
           }
   
  }

O método getMensagem é anotado com @Produces e retorna um objeto String. Embora o tipo produzido seja uma String, pode ser produzido qualquer tipo incluindo interfaces, classes, tipos primitivos, Arrays, entre outros.

Para utilizar o objeto produzido é necessário injetar o mesmo tipo na classe onde ele será utilizado. Segue na Listagem 14 um exemplo de como realizar essa operação.

Listagem 14. Exemplo injetando uma String criada pelo Factory no código anterior.

  package br.com.devmedia.factory;
   
  import javax.ejb.Stateless;
  import javax.ejb.TransactionAttribute;
  import javax.ejb.TransactionAttributeType;
  import javax.inject.Inject;
   
  @Stateless
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public class EventService {
   
           @Inject
           private String mensagem;
   
           public void exemploIniciaServico(){
                     System.out.println("Exemplo de chamada de serviço: " + mensagem);
           }
   
  }

Quando o método exemploIniciaServico é invocado, o valor da string no método produtor é injetado no membro "mensagem" da classe "EventService" e a mensagem é impressa no console. Esta é a forma mais simples possível de implementar o padrão Factory na plataforma Java EE.

A string produzida pelo método getMensagem é injetada corretamente no membro mensagem através do container CDI (Context Dependency Injection).

Em muitos casos provavelmente será necessário retornar diferentes tipos de objetos ao invés de uma simples string. Segue os exemplos nas Listagens 15 a 17 de como isso poderia ser realizado.

Listagem 15. Exemplo de uma classe MensagemA.

  package br.com.devmedia.factory;
  @Alternative
  public class MensagemA {
   
           private String mensagem;
   
           public String getMensagem(){
                     return mensagem;
           }
   
           public void setMensagem (String mensagem){
                     this. mensagem = mensagem;
           }
  }

Listagem 16. Exemplo de uma classe MensagemB.

  package br.com.devmedia.factory;
  @Alternative
  public class MensagemB {
   
           private String mensagem;
   
           public String getMensagem(){
                     return mensagem;
           }
   
           public void setMensagem(String mensagem){
                     this.mensagem = mensagem;
           }
  }

Listagem 17. Exemplo de um Factory que cria mais que uma mensagem.

  package br.com.devmedia.factory;
   
  import javax.enterprise.inject.Produces;
   
  public class EventProducer {
   
           @Produces
           public MensagemA mensagemAFactory(){
                     return new MensagemA();
           }
   
           @Produces
           public MensagemB mensagemBFactory(){
                     return new MensagemB();
           }
  }

Neste exemplo foram criados dois beans MensagemA e MensagemB. Ambos foram anotados com @Alternative que permite a injeção de múltiplos beans com o mesmo ponto de injeção.

Segue na Listagem 18 um exemplo de uma injeção de bean criado pela Factory usando @Inject.

Listagem 18. Injetando beans criados pelo Factory com @Inject.

  package br.com.devmedia.factory;
   
  import javax.ejb.Stateless;
  import javax.ejb.TransactionAttribute;
  import javax.ejb.TransactionAttributeType;
  import javax.enterprise.event.Event;
  import javax.inject.Inject;
   
  @Stateless
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public class EventService {
   
           @Inject
           private MensagemA mensagemA;
   
           @Inject
           private MensagemB mensagemB;
   
           public void exemploIniciaServico(){
                     mensagemA.setMensagem("Exemplo de uma mensagem A");
                     mensagemB.setMensagem("Exemplo de uma mensagem B");
   
                     System.out.println("Exemplo de chamada de serviço: " + mensagemA.getMensagem());
                     System.out.println("Exemplo de chamada de serviço: " + mensagemB.getMensagem());
           }
  }

Na classe EventService acima o conteiner injeta dois beans produzidos pelo Factory nas variáveis membros mensagemA e mensagemB da classe EventService. Uma implementação alternativa é usar as anotações @Qualifier e @interface para marcar o tipo a ser injetado.

Segue o exemplo na Listagem 19 que mostra como usar anotações customizadas para criar dois qualificadores no primeiro exemplo @MensagemCurta e @MensagemGrande no segundo exemplo.

Listagem 19. Usando os qualificadores ShortMessage e LongMessage.

  @Qualifier
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.METHOD, ElementType.FIELD})
  public @interface MensagemCurta {
           //Código aqui
  }
   
  @Qualifier
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.METHOD, ElementType.FIELD})
  public @interface MensagemGrande {
           //Código aqui
  }

Esses qualificadores são usados para anotar métodos produtores e os seus pontos de injeção correspondentes, conforme mostra o exemplo das Listagens 20 e 21.

Listagem 20. Usando os qualificadores para remover ambiguidades dos beans.

  public class EventProducer {
           @Produces @MensagemCurta
           private MensagemA mensagemAFactory(){
                     return new MensagemA();
           }
   
           @Produces @MensagemGrande
           private MensagemB mensagemBFactory(){
                     return new MensagemB();
           }
  }

Listagem 21. Injetando os beans criados usando qualificadores para remover ambiguidades.

  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public class ExemploMensagemCliente {
   
           @Inject @MensagemCurta
           private MensagemA mensagemA;
   
           @Inject @MensagemGrande
           private MensagemB mensagemB;
   
           public void exemploEvento(){
                     mensagemA.setMensagem("Exemplo de uma mensagem grande.");
                     mensagemB.setMensagem("Exemplo de uma mensagem curta.");
   
                     System.out.println(mensagemA.getMensagem());
                     System.out.println(mensagemB.getMensagem());
           }
  }

A anotação @Target especificada determina onde o qualificador pode ser utilizado. O valor pode ser um ou todos os valores: TYPE, METHOD, FIELD, e PARAMETER.

De forma alternativa, a mesma implementação pode ser feita com uso do tipo enum definido na classe @interface conforme mostra o exemplo da Listagem 22.

Listagem 22. Tipo de anotação customizada.

  @Qualifier
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.METHOD})
  public @interface ExemploMeuEventCustomizado {
   
           Type value();
   
           enum Type { LOGGING , MENSAGEM }
   
  }

Através das anotações customizadas podem ser utilizados diferentes métodos para criar objetos strings marcados com essas anotações.

Segue na Listagem 23 as strings produzidas pelos métodos mensagemAFactory e mensagemBFactory

Listagem 23. Exemplo de utilização de anotações customizadas para remover ambiguidade dos beans.

  public class EventProducer {
   
           @Produces
           @ExemploMeuEventCustomizado(ExemploMeuEventCustomizado.Type.LOGGING)
           public String mensagemAFactory(){
                     return "Exemplo de uma mensagem sendo gerada.";
           }
   
           @Produces
           @ExemploMeuEventCustomizado(ExemploMeuEventCustomizado.Type.MENSAGEM)
           public String mensagemBFactory(){
                     return "Exemplo de outra mensagem sendo gerada.";
           }
  }

Na Listagem 24 um exemplo de utilização dessas anotações para anotar métodos produtores e seus pontos de injeção correspondentes.

Listagem 24. Injetando os beans criados com a utilização de anotações customizadas.

  package br.com.devmedia.observer;
   
  import javax.ejb.Stateless;
  import javax.ejb.TransactionAttribute;
  import javax.ejb.TransactionAttributeType;
  import javax.enterprise.event.Event;
  import javax.inject.Inject;
   
  @Stateless
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public class EventService {
   
           @Inject
           @ExemploMeuEventCustomizado(ExemploMeuEventCustomizado.Type.LOGGING)
           private String mensagemA;
   
           @Inject
           @ExemploMeuEventCustomizado(ExemploMeuEventCustomizado.Type.MENSAGEM)
           private String mensagemB;
   
           public void exemploIniciaServico() {
                     System.out.println("Exemplo de chamada de serviço: " + mensagemA);
                     System.out.println("Exemplo de chamada de serviço: " + mensagemB);
           }
  }

Uma possibilidade ainda mais simples é utilizar a anotação @Named ao invés de criar um tipo de anotação própria.

Segue um exemplo na Listagem 25 de como utilizar @Named.

Listagem 25. Usando a anotação @Named para remover ambiguidades.

  package br.com.devmedia.factory;
   
  import javax.enterprise.inject.Produces;
   
  public class EventProducer {
   
           @Produces
           @Named("Logging")
           public String mensagemAFactory(){
                     return "Exemplo de uma mensagem sendo gerada.";
           }
   
           @Produces
           @Named("Mensagem")
           public String mensagemBFactory(){
                     return "Exemplo de outra mensagem sendo gerada.";
           }
   
  }

@Named é utilizado para anotar método produtores e seus pontos de injeção correspondentes. Segue na Listagem 26 um exemplo.

Listagem 26. Injetando usando a anotação @Named

  @Stateless
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public class EventServiceName {
   
           @Inject
           @Named("Logging")
           private String mensagemA;
   
           @Inject
           @Named("Mensagem")
           private String mensagemB;
   
           public void exemploIniciaServico(){
                     System.out.println("Exemplo de chamada de serviço: " + mensagemA);
                     System.out.println("Exemplo de chamada de serviço: " + mensagemB);
           }
  }

É importante salientar que em sistemas mais complexos é sempre indicado utilizar anotações customizadas ao invés de @Named, visto que @Named não é um tipo seguro, podendo vir a causar bugs que o compilador não é capaz de alertar.

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.