[Delphi] Design Patterns GoF – Abstract Factory

[Delphi] Design Patterns - Abstract Factory

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!

 

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 justifico 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.

 

André, exemplifique um cenário de desvantagens do uso de estruturas IF.
Claro! 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:

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:

...
else if Marca = 'Lenovo' then
...

E vou mais além. Suponha também que servidores serão comercializados. Um novo bloco de código (ou método) terá de ser criado com todos esses IFsum 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?

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. Na parte 9 do artigo sobre dicas de desenvolvimento eu detalho um pouco mais sobre elas.

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.

{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:

{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:

{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 você continuar insistindo na forma como apresentei no início do artigo, 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.

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.
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 começar 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!

Exemplo de Abstract Factory com Delphi

 

Abraço, pessoal!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

6 comentários

  1. 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.

    1. 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!

  2. 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

  3. 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?

    1. 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 implementam IDesktop) 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:

      var
        Desktop: IDesktop;
      begin
        Desktop := TInspiron.Create
        Desktop.NomeProcessador := {nome selecionado pelo usuário};
        ...
      end;

      Obrigado pelo feedback. Abraço!

Deixe uma resposta

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

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.