Factory Pattern – O caminho para uma boa arquitetura

Nesse post vou mostrar como podemos usar o Factory Pattern para delegar as responsabilidades do sistema e construir uma arquitetura com manutenção menos custosa e testes unitário mais simples. Isso tudo com um padrão de projeto.

O que são factories

Factories são funções que retornam objetos, ou fábricas de objetos, simples assim! Vamos de exemplo! Antes de ler a explicação reflita um pouco sobre o código a baixo.

O que aconteceu ali?

Linha 1 a 10: Criamos uma função que retorna um objeto (linha 5 a 8) que tem uma função getInfo, que retorna uma string com as variáveis name (linha 2) e kind (linha 3).

Linha 12: Retorno o valor da função getInfo da função doguinho.

Linha 13: Tentativa de acesso a variável name da função doguinho.

Linha 14 e 15: Crio uma variável que recebe o retorno da função doguinho e em seguida invoco a função getInfo. Criamos um novo objeto na verdade.

Na linha 13 conseguimos notar que ao invocar a factory não temos acesso aos seus atributos internos (name e kind), isto porque ao invoca-la retornamos (linha 6) um novo objeto que possui acesso aos atributos.

Encapsulamento em ação

Proteger atributos é conhecido como encapsulamento, que é uma característica muito importante, mas não para proteger seu código de alterações indesejáveis.

Definir “atributos protegidos” na esperança que ninguém com acesso a base de código irá altera-lo é pura ilusão. Qualquer um, inclusive você, pode alterar o encapsulamento fim do expediente de uma sexta-feira, fazer o push e ir embora pra casa. 🧐

Dificilmente “regras de boa conduta” serão mantidas apenas com proteção no código.

A boa do encapsulamento é proteger o que é de responsabilidade daquela parte do sistema, e com as factories conseguimos essa proteção. Ficou vago? Vamos de exemplo!

Arquitetura problemática

Imagine que você tá todo pimpão (ou pimpona) escrevendo o módulo SingIn, responsável pelo cadastro de usuário do sistema (exemplo clichê, eu sei…), num determinado momento você precisa enviar um email de confirmação após o cadastro. Sua arquitetura poderia ser assim:

Arquitetura direta

O módulo SingIn tem como dependência o módulo xEmailService, e em algum momento deve invocar a função de envio de email. Simples e direto, então deve ser o suficiente, certo?! Vamos olhar com calma uma possível implementação.

Olhando para o módulo SingIn podemos identificar alguns problemas que podem ser convertidos em horas extras e noites sem dormir.

Problema 1: SingIn deve conhecer é o módulo de email que ele usa, isso pode ser um problema caso seja necessário alterar para outro serviço de email ou até mesmo quando dois serviços irão coexistir. Se essa abordagem se espalhar pelo resto do sistema qualquer um dos dois casos demandaria um trabalho considerável de alteração e teste.

Problema 2: SingIn deve saber criar objetos de serviço de email, e isso claramente não é algo que SingIn deveria conhecer.

Poderíamos delegar a responsabilidade de criar o serviço de email (com as strings server, login, password) para o módulo xEmailService? Poderíamos, isso resolve o Problema 2 mas não o Problema 1. 🤔 

Factories são a resposta

Mas e se usássemos alguma coisa que cria objetos de serviço de email para SingIn. E se esse coisa fosse tipo uma fábrica de objetos? E se essa coisa fosse uma Factory? Teríamos uma arquitetura dessa forma:

Arquitetura com factory
Factory bacanosa

Agora sim, temos uma factory responsável por criar e retornar objetos de serviço de email, e não apenas um, dois.

Linha 13: Será a função chamada para retornar um serviço de email, ela pode receber o nome do serviço específico de email, ou, caso nenhum seja informado retorna o serviço padrão xEmail.

Linha 3: Cria e retorna o serviço AnotherEmail.

Linha 6: Cria e retorna o serviço xEmail.

Agora podemos chamar a factory direto no SingIn? Podemos, mas não vamos! Vamos fazer um pouco melhor.

Se chamarmos a factory direto em SingIn ele ainda teria a responsabilidade de manipular a factory, isto é, chamar sua função de criação de serviços e, dependendo do caso, saber qual serviço ele vai usar. Eu quero livrar esse responsabilidade do SingIn, quero que ele apenas saiba usar um serviço de email, independente de qual seja.

A arquitetura e implementação ficaria assim:

Novíssima arquitetura

Notem na implementação de SingIn que ele tem o serviço de email injetado durante a criação do objeto, desta forma ele consegue usar o serviço sem ao menos saber qual será o serviço, ele apenas usa.

Agora na implementação de index ele é responsável por saber usar a factory e injeta-la em SingIn, porém os detalhes de implementação de como criar serviços de email estão encapsulados na factory.

Módulo index

Notem, no desenho da arquitetura, que agora SingIn depende indiretamente dos serviços de email que são injetados no seu construtor, ou seja, qualquer alteração nos serviços de email (deste que mantenham as funções com mesmo nome, entradas e saída) não irão demandar alterações em SingIn. O mesmo vale para alterações na forma como criar serviços de email, ocorrerão apenas na factory.

Ou seja, SingIn sabe apenas usar um serviço de email, a factory sabe como criar os serviços, cada serviço de email sabe como enviar o email, e o index é responsável delegar qual serviço será usado e injeta-lo em SingIn.

Com responsabilidades bem definidas, injetando as dependências necessárias e desconectando os módulos conseguimos uma arquitetura com manutenção menos custosa e testes unitários mais simples. Tudo isso com as Factories.

Agora que já aprendemos o Factory Pattern a Ladyzoca está mais feliz.

Ladyzinha

Seção “É ver para crer”

Aqui tem um projeto onde eu uso bastante factories: COVID-MG

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *