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:
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!
Â
Parabéns André por todo o seu conteúdo! Estou no terceiro ano da faculdade (Análise e Desenvolvimento na UFPR) e o professor pediu para fazermos resumos sobre todos os 23 padrões de projetos. Nem uso Delphi na verdade, na matéria o código é em Java e no trabalho uso Node.js. Mas a sua didática é muito boa e consegui entender todos de maneira geral.
Olá, Carlos, agradeço muito pelo seu comentário!
Fiquei muito feliz por saber que os artigos lhe ajudaram. A ideia foi justamente essa: descomplicar as definições dos padrões de projeto!
Os códigos em Delphi são apenas exemplos para complementar o entendimento.
Muito obrigado, Carlos. Grande abraço!