1. Simple Factory (Fábrica Simples)

1.1. Definição

📘Simple Factory permite encapsular a criação de objetos em uma classe que representa uma fábrica. Ela não é de fato um padrão de projeto, podendo ser definida como um modelo para a criação de objetos.

1.2. Aplicabilidade

Sabemos que para instanciar objetos em Java e outras linguagens é utilizado o operador new. Apesar de podermos declarar uma variável com um tipo abstrato, ao instanciar um objeto para tal variável, estamos definindo explicitamente qual tipo concreto será usado.

Se existir uma lógica que define qual tipo concreto será utilizado em um determinado momento e tivermos que utilizar esta lógica em diversas partes do código quando precisarmos instanciar um objeto, podemos utilizar a Simple Factory para encapsular a criação destes objetos. Com isto, evitamos de duplicar esta lógica em vários locais do projeto. Esta lógica normalmente é definida por um bloco de if() que define as condições para a criação dos objetos.

Adicionalmente, considere que temos um tipo abstrato/super-tipo (classe abstrata ou interface) e várias sub-classes que podem ser instanciados. Se utilizarmos new para instanciar uma determinada sub-classe em diferentes locais do sistema e precisarmos substituir estas instâncias por uma sub-classe diferente, teríamos que fazer isso manualmente alterando os vários locais no código. Utilizando a Simple Factory, a criação de objetos de uma determinada sub-classe fica encapsulada e centralizada em um método da Simple Factory.

1.3. Modelagem

A Simple Factory pode ser modelada como demonstrado no diagrama a seguir.

simple factory
Figure 1. Modelagem da Simple Factory

Teremos uma classe SimpleFactory que você deve dar um nome apropriado. Alguns exemplos incluem:

  • SimplePizzaFactory ou PizzaFactory para criar pizzas;

  • SimpleProdutoFactory ou ProdutoFactory para criar produtos.

Cliente é uma classe qualquer que precisa utilizar objetos produzidos pela Factory. Assim, podemos ter várias classes clientes.

O termo "cliente", amplamente utilizado quando falamos de padrões de projeto e POO, representa uma classe que usa os serviços de outra classe, como uma pessoa que usa os serviços de uma empresa.

O TipoAbstrato é um super tipo representado por uma interface ou classe abstrata. Como ele é abstrato, a Factory criará objetos de algum dos sub-tipos disponíveis, de acordo com uma possível lógica que você definir.

Os métodos newObject da SimpleFactory retornam um TipoAbstrato, mas internamente criam um objeto de algum dos sub-tipos existentes. Este método é normalmente definido como estático, assim, não temos que instanciar a fábrica para depois podermos instanciar os objetos que queremos. Observe que pode haver duas versões de tal método. A versão que não recebe parâmetros normalmente vai sempre retornar um tipo específico de objeto. Ao usar tal método em várias partes do sistema, se precisarmos trocar qual o tipo de objeto a ser instanciado, temos que apenas alterar este método. Os locais onde ele estava sendo usado não sofrem nenhuma alteração.

A outra versão do método que possui parâmetros (cujo quantidade e tipos depende do seu problema) normalmente vai permitir, ao desenvolvedor que for usar a Factory, informar qual objeto a ser criado e possíveis dados a serem usados para criá-lo, sem especificar qual classe será instanciada. Adicionalmente, se existir uma determinada lógica necessária para criar qualquer dos tipos de objeto possíveis, tal lógica ficará encapsulada dentro destes métodos newObject. Qualquer alteração neste lógica precisará ser feita em apenas um local.

Um projeto de exemplo para o diagrama apresentado está disponível aqui. Ele deve ser alterado para incluir as mudanças necessárias para o problema específico que você estiver resolvendo com o padrão.

1.4. Princípios utilizados

1.4.1. Open-Closed Principle

Com o Open/Closed Principle (OCP), separamos as partes que mudam, i.e. a lógica de criação de objetos, de dentro da classe que utiliza tais objetos. Isto torna a classe que utiliza a factory "aberta para extensão e fechada para modificação".

1.4.2. Programar para uma "interface" não uma implementação

Programar para uma "interface" não uma implementação (GoF) indica que devemos declar variáveis com tipos abstratos (TipoAbstrato) e usar tipos concretos apenas na instanciação (SubTipo1 …​ SubTipoN).

1.4.3. Dependency Inversion Principle (DIP)

Ao aplicar o Dependency Inversion Principle (DIP), a classe cliente que utiliza a factory não depende mais de tipos concretos, mas sim de tipos abstratos (interfaces ou classes abstratas). Se não utilizássemos a Simple Factory, a classe cliente seria responsável pela lógica de criação dos objetos, dependendo de cada um dos objetos que pode criar, como mostra o diagrama a seguir.

no simple factory
Figure 2. Dependências com a NÃO utilização da Simple Factory

Se observarmos a Figura 1 na Seção 1.3, podemos ver que a classe Cliente depende da SimpleFactory, que depende do TipoAbstrato. Como Cliente usará objetos do TipoAbstrato, ele depende de tal tipo. Então, podemos esquecer a SimpleFactory por um momento e visualizar esta dependência como no diagrama a seguir.

simple factory dip
Figure 3. Dependência com a utilização da Simple Factory: classe Cliente depende de TipoAbstrato

Comparando a Figura 2 com a 3, podemos ver que a relação de dependência foi invertida. A classe Cliente deixou de depender de tipos concretos e passou a depender de um tipo abstrato.

1.5. Exemplos

Podemos utilizar a Simple Factory para definir qual estratégia de leitura de arquivo de retorno de boleto bancário será utilizada. Assim, não teremos que manualmente decidir qual estratégia instanciar cada vez que precisarmos ler arquivos de retorno. Baseado no projeto retorno-boleto-template-funcional, vamos implementar a Simple Factory.

Implemente o projeto seguindo a modelagem abaixo:

simple factory boleto funcional
Figure 4. Simple Factory para a instanciação de estratégias de processamento de arquivos de retorno de boleto bancário.

O código fonte do projeto está disponível aqui (zip). Uma implementação alternativa, que não utiliza programação funcional e descobre dinamicamente o nome da classe para leitura do arquivo de retorno informado pode ser vista aqui (zip). Veja a documentação no link acima para detalhes do projeto.

1.6. Detalhes de Implementação

A forma mais fácil de implementar a Simple Factory, como apresentado na Figura 1, é definir o método newObject na classe SimpleFactory como estático. Assim, não é preciso instanciar uma fábrica para depois criar objetos, basta chamar o método diretamente da classe fazendo SimpleFactory.newObject(). Isto torna o uso da fábrica mais fácil. Se a fábrica não armazena nenhum atributo, pode não haver uma razão específica para exigir que ela seja instanciada para podermos chamar seus métodos. É apenas preciso ter em mente que ao declarar o método como estático, ele não poderá ser modificado em possíveis sub-classes. Se não pretende fazer override do método, não há problema.

O método newObject também pode receber parâmetros para definir qual e/ou como um objeto será criado. O nome do método também deve ser definido adequadamente, como newPizza(), newProduto(), etc.

2. Padrões Relacionados

Padrões que possuem similaridades ou podem ser usados em conjunto:

3. Onde o padrão é usado no JDK

3.1. Calendar

A classe Calendar no JDK é abstrata, logo, não pode ser instanciada. Existem diferentes tipos de calendário que podem ser instanciados, de acordo com a região geográfica definida para o sistema (Locale). O método getInstance() então é responsável por definir qual subclasse de Calendar será usada para instanciar um calendário.

Ele utiliza o método auxiliar createCalendar para isto. Como falado anteriormente, neste caso o método possui parâmetros para definir como criar o objeto. Note que dentro do método ele define diferentes tipos de calendário, como gregorianos (o nosso), japonês e budista.

3.2. JCF no JDK 9+

A partir do JDK 9, as classes Collection introduziram os chamados "Factory Methods". Apesar do nome ser o de outro padrão que veremos à frente, tal padrão não é implementado aqui. O que temos é simplesmente uma Simple Fatory.

A expressão Factory Method expressão é confusa pois pode se referir à:

  • um padrão com este mesmo nome (não é o caso), que veremos a seguir;

  • ou simplesmente a um método fábrica em uma classe/interface responsável por instanciar objetos. Qualquer tipo de fábrica terá um método fábrica.

List<String> listaNomes = List.of("Minha", "Lista", "de", "Strings");
Map<String, Integer> mapaHabitantesCidade =
                        Map.of("Palmas", 217000, "Gurupi", 84000);

3.3. Falando de JDK, pula pro JDK 10

var listaNomes = List.of("Minha", "Lista", "de", "Strings");
var listaPares = List.of(2, 4, 6);
var mapaHabitantesCidade = Map.of("Palmas", 217000, "Gurupi", 84000);

4. Exercícios

4.1. Exportação de dados

Implemente um conjunto de classes que recebe uma lista de produtos e permite exportar tais produtos em diferentes formatos como Tabela HTML, CSV, tabela Markdown. Utilize a diagrama de classes abaixo como base para sua implementação. Tenha em mente que um diagrama desses pode apresentar os detalhes mais importantes como métodos e atributos que já se sabe previamente. No entanto, durante o desenvolvimento, pode-se perceber que são necessários mais métodos e/ou atributos. O diagrama apresenta apenas os elementos públicos e protegidos. Novos métodos que precisem ser incluídos, que serão usados pelos apresentados, normalmente são privados, exatamente por serem apenas métodos auxiliares que não fazem sentido serem chamados de fora das classes.

exportador simple factory
Figure 5. Diagrama de classes para implementação do exportador de lista de produtos para HTML e Markdown

Observe que neste caso, não temos uma classe específica para a Simple Factory. Temos apenas os métodos estáticos newInstance() na interface ExportadorListaProdutos que instanciam objetos da própria classe. Esta é a mesma modelagem utilizada pela classe Calendar do JDK. Métodos estáticos em interfaces é um dos recursos do Java 8.

Existem inúmeras bibliotecas que realizam tal tarefa, mas o objetivo aqui é praticar. Se precisar realmente exportar dados em qualquer formato, tente utilizar uma biblioteca existente. Pode-se exportar dados em XML e JSON utilizando bibliotecas padrões do JDK.

O código fonte do projeto está disponível aqui (zip), mas tente primeiro implementar antes de ver o projeto.

4.2. Tornando a exportação de dados genérica

O projeto anterior permite exportar apenas uma lista de produtos. Se precisarmos exportar outros dados como clientes ou vendas, teremos que criar um novo conjunto de classes para cada tipo de objeto que queremos exportar. Isto é bastante trabalhoso e repetitivo. Neste projeto vamos usar Reflection, um recurso avançado da linguagem Java, com o Simple Factory para conseguir exportar qualquer dado em qualquer formato implementado.

O código fonte do projeto está disponível aqui (zip), mas tente primeiro implementar antes de ver o projeto.