Olá, leitores, como vão?
Conforme prometido, hoje inicio a temporada de artigos sobre Design Patterns! Não serão sequenciais, já que eventualmente postarei artigos sobre outros assuntos. Mesmo assim, é com grande satisfação que dou este primeiro passo.
Para inaugurar, apresento-lhes o Abstract Factory! Confira o artigo e aprenda um pouco mais sobre este padrão!
Introdução
Pessoal, para evitar que o artigo fique extenso, vou dispensar a explicação sobre o que são Design Patterns e seus benefÃcios, ok? Acredito que a maioria (senão todos) já conhecem ou ao menos já ouviram falar destes padrões. No entanto, caso for necessário, posso elaborar um artigo abordando o conceito de Design Patterns de forma geral. Basta deixar um comentário! 🙂
Pois bem, sabemos que uma das caracterÃsticas que “poluem” o código é o excesso de estruturas condicionais, como o IF. Não digo que um projeto não deve ter essas estruturas, mas afirmo que muitas delas são desnecessárias, uma vez que podem ser substituÃdas pelo padrão que vou apresentar neste artigo, mantendo o código mais limpo.
Desvantagens de estruturas condicionais
Considere um sistema de loja de eletrônicos, no qual o usuário seleciona uma marca (como Dell ou Apple) e consulta os produtos, como notebooks, desktops e servidores.
O código executado para exibir os dados dos produtos é listado abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
procedure MostrarDadosProdutos; begin // Dados do notebook if Opcao = 'Dell' then begin EditTamanhoTela.Text := 'Tela de 14 polegadas'; EditMemoriaRAM.Text := '3GB DDR3'; end else if Opcao = 'Apple' then begin EditTamanhoTela.Text := '11.6 polegadas'; EditMemoriaRAM.Text := '4GB DDR3'; end; // Dados do desktop if Opcao = 'Dell' then begin EditProcessador.Text := 'Intel Core i5'; EditTamamhoHD.Text := '1 TB'; end else if Opcao = 'Apple' then begin EditProcessador.Text := 'Intel Core i7'; EditTamamhoHD.Text := '500 GB'; end; end; |
Notou as estruturas condicionais? Sei que podemos aproveitar o mesmo IF para exibir os dados de todos os produtos, mas o código acima é só um exemplo. Na prática, estes IFs podem estar em métodos separados.
Imagine, agora, que essa mesma loja venderá também produtos da Lenovo. Cada método receberá um novo IF:
1 2 3 |
... else if Marca = 'Lenovo' then ... |
E vou mais além. Suponha também que servidores começarão a ser comercializados. Um novo bloco de código (ou método) terá de ser criado com todos esses IFs, um para cada marca.
Em suma, quanto mais marcas e produtos a loja trabalhar, maior será a quantidade de estrutura condicionais. O que aconteceria, por exemplo, se os desenvolvedores esquecessem de adicionar um IF em um destes blocos? Ruim, não?
Abstract Factory
Certa vez, quando eu estava participando de um treinamento sobre Design Patterns, o ministrante mencionou que cada estrutura IF deveria se “transformar” em uma nova classe. Para isso, terÃamos um mecanismo que nos retornaria os dados que precisamos naquele momento sem utilizarmos estruturas condicionais. Chamamos este mecanismo de fábrica, pois, analogicamente, é capaz de “criar” e disponibilizar um “produto”.
Bom, mas nada disso parece fazer sentido se não houver uma aplicação prática, não é?
Usando o mesmo exemplo da loja, o primeiro passo é criar 3 Interfaces: duas para cada produto (notebooks e desktops) e outra para as marcas. Ah, uma observação: Interfaces são recursos extremamente indispensáveis para a implementação de Design Patterns.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
INotebook = interface function BuscarTamanhoTela: string; function BuscarMemoriaRAM: string; end; IDesktop = interface function BuscarNomeProcessador: string; function BuscarTamanhoHD: string; end; IFactoryMarca = interface function ConsultarNotebook: INotebook; function ConsultarDesktop: IDesktop; end; |
Agora, codificaremos a classe concreta de duas marcas que comercializam notebooks e desktops:
- Dell, que possui o Vostro e Inspiron;
- Apple, que possui o MacBook e iMac.
Vale lembrar que, como elas implementam IFactoryMarca
, devem obrigatoriamente declarar os métodos assinados nessa Interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
{TDell} TDell = class(TInterfacedObject, IFactoryMarca) function ConsultarNotebook: INotebook; function ConsultarDesktop: IDesktop; end; function TDell.ConsultarNotebook: INotebook; begin result := TVostro.Create; end; function TDell.ConsultarDesktop: IDesktop; begin result := TInspiron.Create; end; {TApple} TApple = class(TInterfacedObject, IFactoryMarca) function ConsultarNotebook: INotebook; function ConsultarDesktop: IDesktop; end; function TApple.ConsultarNotebook: INotebook; begin result := TMacBook.Create; end; function TApple.ConsultarDesktop: IDesktop; begin result := TIMac.Create; end; |
Em seguida, vamos criar as classes referentes aos notebooks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{TVostro} TVostro = class(TInterfacedObject, INotebook) private function BuscarTamanhoTela: string; function BuscarMemoriaRAM: string; end; function TVostro.BuscarTamanhoTela: string; begin result := '15 polegadas'; end; function TVostro.BuscarMemoriaRAM: string; begin result := '3GB DDR3'; end; {TMacBook} TMacBook = class(TInterfacedObject, INotebook) private function BuscarTamanhoTela: string; function BuscarMemoriaRAM: string; end; function TMacBook.BuscarTamanhoTela: string; begin result := '11.6 polegadas'; end; function TMacBook.BuscarMemoriaRAM: string; begin result := '4GB DDR3'; end; |
E, finalmente, as classes relacionadas aos desktops:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{TInspiron} TInspiron = class(TInterfacedObject, IDesktop) private function BuscarNomeProcessador: string; function BuscarTamanhoHD: string; end; function TInspiron.BuscarNomeProcessador: string; begin result := 'Intel Core i5'; end; function TInspiron.BuscarTamanhoHD: string; begin result := '1 TB'; end; {TIMac} TIMac = class(TInterfacedObject, IDesktop) private function BuscarNomeProcessador: string; function BuscarTamanhoHD: string; end; function TIMac.BuscarNomeProcessador: string; begin result := 'Intel Core i7'; end; function TIMac.BuscarTamanhoHD: string; begin result := '500 GB'; end; |
Nossa, André, quanto código!!!
Sim, foi a mesma coisa que pensei enquanto estudava Design Patterns, porém, acredite: se um desenvolvedor continuar insistindo na forma como apresentei no inÃcio do artigo (com estruturas condicionais), em pouco tempo o seu código ficará bem maior do que a implementação apresentada acima. O nÃvel de abstração que alcançamos com esse padrão (criando Interfaces e classes com responsabilidade única), permitirá com o que a nossa arquitetura se torne bastante desacoplada, reduzindo as linhas de código a longo prazo.
Bom, pessoal, com tudo já pronto, é hora de conferirmos toda a mágica do padrão. Veja abaixo como ficou o método MostrarDadosProdutos
. O único IF aparece somente nas primeiras linhas para instanciar a marca selecionada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
procedure MostrarProdutos; var Marca: IFactoryMarca; Notebook: INotebook; Desktop: IDesktop; begin // instancia a marca -> único IF da aplicação if Opcao = 'Dell' then Marca := TDell.Create else if Opcao = 'Apple' then Marca := TApple.Create; // consulta (constrói) os objetos Notebook := Marca.ConsultarNotebook; Desktop := Marca.ConsultarDesktop; // exibe os dados EditTamanhoTela.Text := Notebook.BuscarTamanhoTela; EditMemoriaRAM.Text := Notebook.BuscarMemoriaRAM; EditProcessador.Text := Desktop.BuscarNomeProcessador; EditTamamhoHD.Text := Desktop.BuscarTamanhoHD; end; |
Ué, onde estão os  outros IFs? 🙂
Com um pouco mais de implementação, é possÃvel remover até o IF no inÃcio do método, mas isso é assunto para outro artigo.
Conclusão
Com o Abstract Factory, reduzimos as estruturas condicionais, facilitamos a manutenção do código e, acima de tudo, mantemos a escalabilidade da arquitetura, ou seja, se a loja passar a vender uma nova marca e/ou um novo produto, basta criar apenas novas classes e alterar a nossa fábrica. O Abstract Factory “se vira” com o resto.
Agora, olhando o código novamente, tente imaginar o quão fácil seria se precisássemos adicionar a marca “Lenovo”. Esse é o objetivo! 🙂
Caso você tenha ficado com alguma dúvida, baixe um exemplo no link abaixo (com alguns aprimoramentos) ou deixe um comentário!
Abraço, pessoal!
Excelente artigo André, meus sistemas possui centenas de if, else kkkkk. Vou estudar melhor esse artigo e tentar adptar para meu uso. E obrigado por apresentar isso pois eu não tinha o menor conhecimento sobre isso. Gostaria de saber se vc poderia escrever um artigo sobre NF-e pois eu tenho muitas dúvidas com respeito a integração, e acredito que outras pessoas tbm tenham dúvidas a respeito. Obrigado e continue esse excelente trabalho.
Fala, André, tudo certo?
Muito obrigado pelo feedback sobre o artigo! Espero que este padrão realmente possa ajudá-lo em seus projetos.
André, infelizmente não trabalhei com NF-e, então não tenho uma bagagem de conhecimento satisfatória para elaborar um artigo.
Mesmo assim, recomendo os componentes do projeto ACBr, bastante conhecido por programadores Delphi que já trabalharam com este segmento.
http://www.projetoacbr.com.br/forum/
Abraço!
fantastico celestino! 😀
sempre q vc faz esse recesso de fim de ano volta melhor, kkk
vc falou q é possÃvel remover esse IF do inicio mas q seria abordado em outro artigo, entao aborde pf, pq ate agora n cosnegui imaginar como tirar eles… 😛
abs
E aÃ, Conde, tudo bem?
Pode deixar que logo logo vou abordar como remover esse último IF! 🙂
Obrigado pelo elogio, Conde!
Abração!
Bom dia, Parabéns pelo trabalho, uma iniciativa como essa não tem preço, perincipalmente a iniciantes leigos como eu.
Tive uma dúvida. Se por acaso eu quiser escolher o processador “Intel Core i3”, “Intel Core i5”, “Intel Core i7”, teria que fazer um If na função “TInspiron.BuscarNomeProcessador”. Seria isso?
Olá, Wandelei, como vai?
No exemplo deste artigo, os dados são apenas consultados, e não inseridos ou alterados. No caso de uma inserção, a classe
TInspiron
(assim como as outras classes que implementamIDesktop
) teria uma property referente ao nome do processador. Dessa forma, o usuário poderia escolher o processador durante o cadastro do produto, atribuindo-o à essa property, como no exemplo abaixo:Obrigado pelo feedback. Abraço!
Simples, objetivo e prático. Meus parabéns e obrigado por me ajudar a entender esse padrão.
Obrigado pelo comentário, Igor!
Grande abraço!
Cara, muito bom seu artigo, foi bem didático, de fácil entendimento. Muito obrigado por compartilhar seu conhecimento.
Eu que agradeço pelo feedback, Warley!
Fico feliz por saber que gostou do artigo.
Grande abraço!
Meu querido, esse [Delphi] Design Patterns GoF – Retrospectiva tem em video? algum curso especifico para leigos como eu?
Boa noite, Germano!
Infelizmente não gravei vÃdeos para essa série de artigos. Quem sabe futuramente!
Abraço!