[Delphi] Design Patterns GoF – Factory Method

[Delphi] Design Patterns - Factory Method

Boa noite, leitores!
Peço desculpas pela ausência! Depois de um mês bastante agitado com mudança de apartamento, estou de volta!
O tema dessa vez é o padrão de projeto Factory Method, também da família de padrões criacionais. Confira o artigo para compreender este padrão e identificar as principais diferenças quando comparado ao Abstract Factory.

 

Quando comecei a ler sobre o Factory Method, fiquei perplexo. Quanto mais pesquisava sobre o padrão, mais eu pensava: “Caramba, este padrão é idêntico ao Abstract Factory!”. Porém, eu sabia que havia alguma diferença e que deveria identificá-la para produzir o novo artigo do blog, então continuei insistindo. Eis que, depois de algum tempo, encontrei um tópico bem interessante no StackOverflow (em inglês) que explica a diferença entre eles em poucas palavras (parafraseado):

 

A principal diferença entre Factory Method e Abstract Factory é que o primeiro representa um método simples, enquanto o segundo é um objeto. Por ser um método, o Factory Method pode ser sobrescrito em subclasses. O Abstract Factory, por sua vez, é um objeto que pode conter várias fábricas.

 

Mesmo com essa definição, a diferença ainda pode parecer um pouco “ofuscada”.
Mas, não se preocupe! O exemplo apresentado neste artigo o ajudará a compreender melhor todo este contexto.
Pois bem, antes de introduzir a parte prática, vale fazer um repasse na definição teórica. O Factory Method é um padrão de projeto que encapsula a criação de um objeto através de um método (por isso o “method” no nome). Por meio de parâmetros, a fábrica (factory) decide qual produto (product) deve criar e retornar para o cliente. Com o produto pronto, podemos acessar os métodos e propriedades conforme necessário.

Mas esse comportamento não é semelhante ao Builder, abordado no artigo anterior?
Não. No Builder, a lógica de criação de objetos está na aplicação, ou seja, criamos o construtor (Builder), mas temos que “orientá-lo” para criar o produto. No Factory Method, a criação do objeto está dentro da fábrica. Informamos um parâmetro e deixamos que a fábrica decida quem criar. Mesmo assim, observe que a família de padrões criacionais possui, em termos gerais, o mesmo objetivo: solucionar problemas relacionados à criação de objetos.

 

Chega de teoria, né?
Para criar o exemplo deste artigo, imaginei uma financiadora. Neste cenário, informaremos o valor, a quantidade de parcelas e o prazo de pagamento deste valor. O Factory Method irá criar um objeto de tipo de prazo com todas as informações geradas com estes parâmetros, como o número da parcela, a data de vencimento, o valor atualizado e o valor total a pagar. É bom ressaltar que devemos nos atentar ao modo como o tipo de prazo será criado e retornado. É bem aí que o padrão irá trabalhar.

Em primeiro lugar, criaremos as Interfaces. Uma para o tipo de prazo e outra para o Factory Method:

type
  ITipoPrazo = interface
    function ConsultarDescricao: string;
    function ConsultarJuros: string;
    function ConsultarProjecao(const Valor: real; const QtdeParcelas: integer): string;
    function ConsultarTotal: string;
  end;
 
  IFactoryMethod = interface
    function ConsultarPrazo(const Prazo: string): ITipoPrazo;
  end;

Agora, partindo para as classes concretas, criaremos dois tipos de prazo: mensal e anual.

type
  TPrazoMensal = class(TInterfacedObject, ITipoPrazo)
  public
    function ConsultarDescricao: string;
    function ConsultarJuros: string;
    function ConsultarProjecao(const Valor: real;
      const QtdeParcelas: integer): string;
  end;
 
  TPrazoAnual = class(TInterfacedObject, ITipoPrazo)
  public
    function ConsultarDescricao: string;
    function ConsultarJuros: string;
    function ConsultarProjecao(const Valor: real;
      const QtdeParcelas: integer): string;
  end;

{ TPrazoMensal }
 
function TPrazoMensal.ConsultarDescricao: string;
begin
  result := 'Prazo Mensal para Pagamento';
end;
 
function TPrazoMensal.ConsultarJuros: string;
begin
  result := 'Juros de 3,1% simples ao mês' + sLineBreak;
end;
 
function TPrazoMensal.ConsultarProjecao(const Valor: real;
  const QtdeParcelas: integer): string;
var
  Projecao: TStringList;
  Contador: smallint;
  ValorAjustado: real;
  DataParcela: TDateTime;
begin
  ValorAjustado := Valor;
  DataParcela := Date;
  Projecao := TStringList.Create;
  try
    for Contador := 1 to QtdeParcelas do
    begin
      ValorAjustado := ValorAjustado + (Valor * 0.031);
      DataParcela := IncMonth(DataParcela, 1);
 
      Projecao.Add(Format('Parcela %.2d (%s): %m',
        [Contador, DateToStr(DataParcela), ValorAjustado]));
    end;
    result := Projecao.Text;
  finally
    FreeAndNil(Projecao);
  end;
end;

{ TPrazoAnual }
 
function TPrazoAnual.ConsultarDescricao: string;
begin
  result := 'Prazo Anual para Pagamento';
end;
 
function TPrazoAnual.ConsultarJuros: string;
begin
  result := 'Juros de 10,5% simples ao ano' + sLineBreak;
end;
 
function TPrazoAnual.ConsultarProjecao(const Valor: real;
  const QtdeParcelas: integer): string;
var
  Projecao: TStringList;
  Contador: smallint;
  ValorAjustado: real;
  DataParcela: TDateTime;
begin
  ValorAjustado := Valor;
  DataParcela := Date;
  Projecao := TStringList.Create;
  try
    for Contador := 1 to QtdeParcelas do
    begin
      ValorAjustado := ValorAjustado + (Valor * 0.105);
      DataParcela := IncMonth(DataParcela, 12);
 
      Projecao.Add(Format('Parcela %.2d (%s): %m',
        [Contador, DateToStr(DataParcela), ValorAjustado]));
    end;
    result := Projecao.Text;
  finally
    FreeAndNil(Projecao);
  end;
end;

Claro, sei que o método “ConsultarProjecao” pode ser refatorado para reaproveitamento nas duas classes, mas, como este exemplo é didático, decidi mantê-lo dessa forma para facilitar a compreensão.
Em seguida, implementaremos também a classe concreta do Factory Method:

type
  TFabricaPrazos = class(TInterfacedObject, IFactoryMethod)
    function ConsultarPrazo(const Prazo: string): ITipoPrazo;
  end;
 
{ ... }
 
{ TFabricaPrazos }
 
function TFabricaPrazos.ConsultarPrazo(const Prazo: string): ITipoPrazo;
begin
  // A "decisão" de qual classe será criada está dentro da fábrica
 
  if Prazo = 'Mensal' then
    result := TPrazoMensal.Create
  else if Prazo = 'Anual' then
    result := TPrazoAnual.Create;
end;

 

Pronto, pessoal!
O próximo passo é consumir essas classes. Para isso, crie um formulário com os seguintes componentes:

  • TEdit, com o nome “EditValor”;
  • TEdit, com o nome “EditQtdeParcelas”;
  • TComboBox, com o nome “ComboBoxPrazoPagamento” e com os itens “Mensal” e “Anual”;
  • TButton, com o nome “ButtonGerarProjecao”;
  • TMemo, com o nome “Memo”.

A tela deverá ficar parecida com a imagem abaixo:

Formulário de exemplo de aplicação do Design Pattern Factory Method

 

No botão de geração de projeção, faremos todo o mecanismo do padrão:

var
  // variável para instanciar a fábrica
  FabricaPrazos: IFactoryMethod;
  // objeto que será retornado pela fábrica
  TipoPrazo: ITipoPrazo;
  // variável que armazenará o valor digitado tela
  Valor: real;
  // variável que armazenará a qtde de parcelas digitada na tela
  QtdeParcelas: integer;
begin
  // instancia a fábrica (Factory Method)
  FabricaPrazos := TFabricaPrazos.Create;
  // obtém o produto, baseado no parâmetro informado
  TipoPrazo := FabricaPrazos.ConsultarPrazo(cmbPrazoPagamento.Text);
 
  // preenchimentto das variáveis
  Valor := StrToFloatDef(EditValor.Text, 0);
  QtdeParcelas := StrToIntDef(EditQtdeParcelas.Text, 0);
 
  // impressão do conteúdo do objeto
  Memo.Lines.Clear;
  Memo.Lines.Add(TipoPrazo.ConsultarDescricao);
  Memo.Lines.Add(TipoPrazo.ConsultarJuros);
  Memo.Lines.Add(TipoPrazo.ConsultarProjecao(Valor, QtdeParcelas));
end;

Execute a aplicação, informe um valor, a quantidade parcelas, o prazo de pagamento e clique no botão de geração de projeção. O componente Memo será preenchido com o conteúdo da classe conforme o prazo selecionado no ComboBoxPrazoPagamento. Importante: Veja que no código no botão não há nenhuma instrução IF! 🙂

 

Qual a vantagem de utilizar este padrão?
Bom, além de evitar o IF, como apontei no parágrafo anterior, uma grande vantagem é a facilidade manutenção, principalmente evolutiva. Sabe por quê? Caso seja necessário adicionar um novo prazo (semestral, por exemplo), o código do botão permanecerá exatamente o mesmo! A alteração será feita dentro da fábrica, que receberá uma nova condição para retornar o objeto deste tipo de prazo. Para apresentar este cenário, baixe o exemplo do Factory Method no link abaixo e veja os comentários no código!

Exemplo de Factory Method com Delphi

 

Ainda fiquei com uma dúvida: qual é a diferença entre os três padrões abordados até agora no blog?
Essa parte é importante!
Com o Abstract Factory, é possível trabalhar com múltiplas fábricas. Pode-se dizer, então, que é um padrão de “fábrica de fábricas”. No primeiro artigo, portanto, instanciamos uma fábrica (marca) e depois “construímos” os produtos (notebooks e desktops). Podemos criar novas fábricas, como “hardware”, e trabalhar com novas fábricas dentro dela, como “placa de vídeo” e “disco rígido”.
No Factory Method, acontece um pouco diferente. Não temos um conjunto de fábricas, mas apenas uma única fábrica que nos retorna um tipo de objeto conforme algum(ns) parâmetro(s) informados. A lógica de criação do produto fica a critério da fábrica, enquanto o cliente apenas recebe a instância. No exemplo deste artigo, o parâmetro é o prazo de pagamento, no qual faz a fábrica “decidir” quem instanciar. Sempre que uma nova condição (como um novo prazo) for adicionada, apenas a fábrica será alterada para considerar o novo produto (método “TFabricaPrazos.ConsultarPrazo”). Ao mesmo passo, se uma nova informação precisa ser adicionada nos tipos de prazo (como o total a pagar), basta alterar somente as classes “TPrazoMensal” e “TPrazoAnual”, ou seja, a fábrica permanece inalterada, já que é apenas responsável por construir o produto. Observe que as responsabilidades ficam bem delimitadas: o cliente, a fábrica e o produto. A alteração em um deles não impacta fortemente no outro.
O Builder, por sua vez, é um construtor de objetos complexos. O propósito deste padrão é montar um objeto “em partes”. Um exemplo clássico do Builder é o padrão MVC. Podemos ter uma classe Builder que monta as camadas Model, View e Presenter e nos retorna um componente pronto. Outro exemplo é a montagem de um relatório com várias seções. Podemos utilizá-lo para compor um relatório com seções dinâmicas de acordo com os parâmetros que o usuário informou. No artigo sobre o Builder, fiz a montagem de cestas de produtos, nos quais poderiam ser substituídos por objetos, constituindo, então, um produto complexo.

 

Leitores, espero que tenham entendido a diferença entre estes padrões. Qualquer dúvida, deixe um comentário!
Até a próxima! Abraço!


 

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

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.