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.
Introdução
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.
Agora chega de teoria, né? 🙂
Exemplo de codificação do Factory Method
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.
Interfaces
Em primeiro lugar, criaremos as Interfaces. Uma para o tipo de prazo e outra para o Factory Method:
1 2 3 4 5 6 7 8 9 10 11 |
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; |
Classes concretas
Agora, partindo para as classes concretas, criaremos dois tipos de prazo: mensal e anual.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; |
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 36 37 |
{ 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; |
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 36 37 |
{ 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.
Classe do Factory Method
Em seguida, implementaremos também a classe concreta do Factory Method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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:
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 |
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! 🙂
Conclusã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.
Diferenças entre os 3 padrões abordados até o momento
Com o Abstract Factory, é possÃvel trabalhar com múltiplas fábricas. Pode-se dizer, 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!
Ola! André, estou tentando encaixar o Factory Method no meu projeto, me tire uma dúvida por gentileza se possÃvel. Vejo que na mesma classe TipoPrazos, você cria os Planos Mensal, Semestral, Anual, se eu colocasse esses planos em classes separadas, ficaria uma boa prática, o que você me diz? Obrigado e parabéns sempre pelos exelentes conteúdos postados. Deus te abençoe Sempre!!!
Olá, Matheus! Obrigado pelo comentário.
Eu criei as 3 classes na mesma unit apenas para fins didáticos. Na prática, é bastante recomendável criar cada classe em sua própria unit por 3 motivos: Clean Code, coesão na arquitetura e evitar o conceito de “classes amigas”, no qual uma classe pode enxergar os atributos privados de outro classe apenas pelo fato de estar na mesma unit.
Espero ter respondido!
Abraço!