Olá pessoal, hoje vamos abordar um assunto bem legal dentro do mundo de programação. Vou colocar aqui e também em meu canal no youtube uma série de artigos e vídeos sobre Padrões de Projeto ou Design Patterns.
Neste artigo vamos abordar como implementar o Padrão de Projeto “Abstract Factory” em uma API Rest utilizando o C# como linguagem de programação.
Antes de mais nada, é super importante que vocês estejam confortáveis com os conceitos do paradigma de Programação Orientada a Objetos e SOLID.
Caso queiram entender e praticar os conceitos de SOLID, podem assistir esse vídeo aqui do Eduardo Pires.
Bom pessoal, sem mais delongas, vamos lá!
O que é o padrão de projeto Abstract Factory?
O Abstract Factory faz parte da família de padrões “criacionais”. Um padrão de projeto criacional nos permite fazer uso de mecanismos para a criação de objetos, aumentando a flexibilidade e reutilização do nosso código, nos ajuda também a alcançar uma alta coesão e baixo acoplamento.
Em termos técnicos o Abstract Factory nos permite criar familias de objetos relacionados ou dependentes, sem a necessidade de especificar suas classes concretas.
Calma, para alguns pode ser um pouco confuso entender o que exatamente esse padrão de projeto faz, mas vamos aos poucos. O Abstract Factory possui alguns participantes, vamos entender cada um deles e suas responsabilidades.
Os participantes são:
Abstract Factory: Conhecido como “Fábrica de produtos abstratos”, este é responsável por criar classes/produtos abstratos. Ou seja, dentro dessa classe criaremos métodos abstratos que retornam para quem o chama uma classe/produto abstrato.
Concrete Factory: Conhecido como “Fábrica de produtos concretos”, este é responsável por criar classes/produtos concretos. Ou seja, dentro dessa classe implementaremos os métodos herdados da nossa fábrica abstrata, mas ao invés de retornar uma classe/produto abstrato, retornaremos uma classe/produto concreto.
Abstract product: Conhecido como “Produto abstrato”, este é apenas uma classe/produto abstrata que definimos para exemplificar alguma coisa do mundo real.
Concrete product: Conhecido como “Produto concreto”, este é apenas uma classe/produto concreto, onde implementamos uma classe/produto abstrata.
Client: Este entendemos como consumidor, porém, essa classe apenas consome os produtos abstratos gerados a partir de nossas fabricas abstratas. O Client não é obrigado a saber como as coisas estão sendo implementadas, por isso ele consome apenas a fábrica abstrata e produtos abstratos.
Esses diagramas abaixo, são utilizados para exemplificar esses participantes, veja:
Temos também este aqui:
Bom, depois de toda essa teoria sobre o Abstract Factory, vocês devem estar se perguntando: “Tá, e ai? Como implementar isso na prática?”.
Eu criei uma demo de uma API utilizando ASP.NET 5 e C#.
Vamos lá!!!!
É importante termos instalado na máquina o Visual Studio Community e também o SDK do .NET 5.
Vamos criar um novo projeto no Visual Studio do tipo ASP.NET Core Web API.
Escolha um nome para seu projeto e o local onde será salvo.
E por último e selecione a versão do .NET 5, caso vocês tenha outras versões.
Legal, com o nosso projeto criado podemos agora começar a implementar de fato o nosso padrão de projeto. Primeiramente vamos remover as classes que são criadas automaticamente para nós. Podemos excluir os arquivos “WeatherForecast.cs” e “WeatherForecastController.cs”.
Ótimo, agora podemos começar a criar os diretórios para separar a nossa aplicação em responsabilidades especificas. Vamos lá, no seu Visual Studio crie as seguintes pastas (Clients, Domain, Factories) conforme imagem:
Agora de fato vamos começar a colocar a mão na massa e aplicar o padrão de projeto. Mas antes, gostaria apenas de explicar qual vai ser o nosso cenário.
Imaginem que vocês tem um cliente que possui uma loja de alimentos e esta loja é especialista na venda de alimentos/embalagens orgânicas e inorgânicas. A partir disso, o cliente pediu que fosse construida uma API Rest para retornar uma lista de produtos de acordo com o tipo que ele especificar na chamada HTTP, podendo ele ser “Orgânico” ou “Inorgânico”.
Ótimo, depois de entender a necessidade… bora meter a mão no código.
Vamos criar primeiramente os nossos produtos abstratos, pensando no contexto da nossa aplicação os nossos produtos inicialmente serão: “Food” e “Packing”.
Como esses caras vão ser nossos produtos abstratos, que serão herdados por produtos concretos e utilizados na nossa fábrica abstrata, criei uma pasta “Base” e coloquei as classes lá dentro.
Food e Packing vão ficar assim, respectivamente:
Podemos criar também o enum “TypeProduct” que identifica qual o tipo de produto. A classe ficaria assim:
Vamos aproveitar e já criar também os nossos produtos concretos, ou seja, as classes que vão implementar nossos produtos abstratos. Se pararmos para pensar um pouco, os nossos produtos concretos vão ser: “FoodOrganic”, “FoodInorganic”, “PackingOrganic” e “PackingInorganic”. Bora implementar essas classes.
Primeiramente crie os arquivos na pasta Domain, dessa forma:
E agora as implementações das nossas classes/produtos concretos:
Depois de criarmos os nossos produtos abstratos e também os produtos concretos, podemos começar a pensar em como vai ficar a nossa fábrica abstrata. Temos em mente que a nossa fábrica é responsável por criar produtos abstratos, então, já sabemos que ela vai ser responsável por criar para nós os produtos Food e Packing.
Vamos a criação da classe, dentro do diretório Factories crie um outro diretório chamado “AbstractFactories” e dentro dele crie a classe “ProductFactory”, vamos criar nessa classe dois métodos abstratos responsáveis por criar nossos produtos abstratos Food e Packing.
Localização do arquivo nos diretórios:
Nossa fábrica ficará assim:
Beleza pessoal, chegamos até aqui, não vamos desistir hein… Bom, depois criar nossas fábricas abstratas nos resta implementar nossas fábricas concretas, essas que serão responsáveis por criar nossos produtos concretos “FoodInorganic”, “FoodOrganic”, “PackingInorganic ”e “PackingOrganic”.
Vamos lá!
Dentro do diretório “Factories ” crie duas classes com os seguintes nomes: “ProductOrganicFactory” e “ProductInorganicFactory”. Não podemos esquecer de que nossas fábricas concretas vão herdar de nossa fábrica abstrata, com isso vamos ter a responsabilidade de implementar os métodos abstratos da nossa fábrica abstrata, ou seja, vamos especializar esses carinhas em nossas fábricas concretas.
Localização dos arquivos no diretório:
Nossas fábricas ficarão assim:
Depois de implementar nossos produtos e fabricas, sendo eles abstratos e concretos só nos falta implementar nosso “Client”. Vamos nessa!
Dentro do diretório “Clients” crie a classe “ProductClient”, vou explicar a implementação do nosso client em partes para ficar mais fácil de apresentar o que foi feito.
Primeiramente criamos duas propriedades na nossa classes dos nossos produtos abstratos. Ficando assim:
Depois criamos o nosso construtor:
Repare que o nosso construtor esta um pouco diferente do que normalmente fazemos com a famosa injeção de dependência hahaha, mas calma, vamos entender o porque estamos fazendo assim.
Nós temos dentro da nossa classe “ProductClient” duas propriedades privadas somente leitura, essas propriedades representam os nossos produtos abstratos.
No nosso construtor estamos recebendo como parâmetro o “ProductFactory”, que nada mais é do que nossa fábrica abstrata e dentro do nosso método construtor estamos criando as instâncias para os nossos produtos.
Mas porque não podemos dar um “new” logo em cada produto e já criar a instância que queremos?
Porque se fizermos isso, vamos perder todos os poderes que esse padrão de projeto nos oferece (alta coesão, baixo acoplamento), entre outras coisas.
Lembre-se, o nosso cliente não tem a obrigação e nem deve saber como as coisas estão sendo implementadas, ele apenas consome os produtos e fábricas abstratas.
Continuando…
Para facilicar a criação de nossas fábricas concretas, vamos criar um método estático dentro da classe “ProductClient” que será responsável por retornar para nós uma instância do nosso client já com a fábrica concreta que precisamos.
O nosso método ficará dessa forma:
Ainda dentro da classe “ProductClient” vamos criar mais dois métodos que retornarão para nós uma lista de “Food” e “Packing”, já temos esses métodos criados dentro dos nossos produtos abstratos, vamos apenas dar um return nesses métodos das nossas classes.
Ótimo pessoal, já temos todo o padrão de projeto “Abstract Factory” implementado, só nos resta agora ver ele na prática, funcionando.
Como comentamos no começo desse artigo, a proposta era implementar esse padrão de projeto utilizando uma API, vamos fazer isso.
Dentro do diretório “Controllers” crie uma classe chamada “ProductsController”, ficando asssim:
Olha que legal pessoal, dentro da nossa controller vamos criar dois métodos, um para buscar “food”e outro para buscar “packing”, via query params estamos recebendo o tipo de produto que queremos pesquisar, esse tipo é o mesmo dos nossos produtos abstratos.
Quando chamamos qualquer um desses métodos, a primeira ação que executamos é a chamada do método estático “CreateProductClient” e passamos o tipo que está vindo da nossa requisição.
Se o usuário passar para nosso método “SearchFood” o tipo “Organic”, será criada uma instância no nosso “ProductClient” e a fábrica concreta que será criada no construtor nesse caso é a “ProductOrganicFactory”.
Veja o exemplo em debug na imagem:
No nosso construtor a instancia que esta sendo passada é da nossa fábrica concreta “ProductOrganicFactory”, isso só é possível por conta da herança que realizamos da nossa fábrica abstrata “ProductFactory”.
Logo abaixo, chamamos o método “SearchFood” e se pararmos para analisar, nosso “client” está chamando esse método do nosso produto abstrato “Food” que declaramos no inicio da classe. E agora vem a pergunta:
E como que a nossa aplicação sabe que ela tem que retornar uma lista de “Food” do tipo “Organic”?
Simples, através da herança que utilizamos em nossas fabricas e produtos concretos(as).
Quando o método “SearchFood” ser acionado, a implementação dele vai estar no nosso produto concreto “ProductOrganic” porque a fábrica que criou o produto foi a fábrica concreta “ProductOrganicFactory”. As imagens a seguir mostram o fluxo da aplicação quando chamamos o método “SearchFood” da nossa controller.
Recebendo o tipo “Organic” no nosso método de buscar “foods”:
Criando uma instância de “ProductClient” e passando no construtor uma instância da fábrica “ProductOrganicFactory” de acordo com o tipo recebido.
No construtor de “ProductClient” estamos criando produtos concretos de “Food” e “Packing” através na nossa fábrica concreta “ProductOrganicFactory” recebida no construtor, ou seja, esta sendo criado instâncias de “FoodOrganic” e “PackingOrganic”.
Quando chamamos o método “SearchFood”, a lista que será retornada é uma lista do tipo “List”, ou seja, a execução deste carinha esta ocorrendo dentro do nosso produto concreto “FoodOrganic”.
No nosso navegador vamos ter o seguinte retorno para o tipo que passamos:
Se fosse solicitado o tipo “Inorganic”, seria este o retorno apresentado:
Enfim, terminamos pessoal. Mas antes gostaria de fazer algumas observações.
Com a implementação desse padrão de projeto, conseguimos atender algumas das recomendações do SOLID, sendo elas:
SRP, onde cada classe tem a sua responsabilidade, não dependendo e nem implementando coisas que seriam responsabilidade de outras classes.
OCP, esse princípio é atendido quando criamos as classes abstratatas “Food” e “Packing”, que podem ser herdadas e especializadas. Se por um acaso, um dia surgir um novo tipo de produto como “Industrial”, podemos especializar essas classes como, “FoodIndustrial” e “PackingIndustrial” por exemplo.
LSP, quando criamos nossas abstrações de fábricas abstratas e fábricas concretas, estamos garantindo que este principio seja atendido.
Os outros dois princípios não foram implementados neste exemplo, mas fica aberto para discussão e melhorias caso localizem algo que poderia ser implementado.
O padrão de diretórios criado foi apenas um exemplo para demostrar como implementar este padrão de projeto, caso veja a necessidade de utilizar esse padrão, lembre-se de adequar a implementação a arquitetura que voce está utilizando.
Este exemplo não é uma receita de bolo que pode ser seguida e copiada sempre, busque abstrair o que foi passado aqui e implemente de acordo com com a necessidade e arquitetura do seu projeto.
Dessa vez é isso pessoal, muito obrigado pra quem chegou até aqui.
Curta e compartilhe com seus amigos que estão aprendendo sobre este padrão de projeto, é sempre bom ter uma visão diferente de como fazer as coisas.
Críticas construtivas são sempre bem vindas, caso localizem algo que possa ser melhorado, deixa ai nos comentários ou me mande uma mensagem.
Logo vou postar um vídeo no youtube mostrando o passo a passo da nossa demo. Pra quem curte conteúdo em vídeo, vai ser legal.
Links:
Projeto no github: aqui
Vídeo no youtube: aqui
Twitter: aqui
youtube: aqui
polywork: aqui
Top comments (0)