Saudações, pessoal!
Em algum momento, enquanto você codificava alguma funcionalidade, já identificou a necessidade de “envelopar” uma rotina complexa em apenas uma classe? Esse procedimento, também conhecido como wrapper, é basicamente o propósito do padrão de projeto que apresentarei neste artigo: o Façade. Você verá que o seu uso é relativamente comum na programação. Acompanhe!
Introdução
Em primeiro lugar, é Facade ou Façade?
Cada um fala de um jeito, não é? Bom, os dois termos estão corretos. “Facade” é mais popular no idioma inglês. “Façade”, com cedilha, leva um aspecto francês na palavra e é mais comum na Europa. No Brasil, geralmente usamos a segunda opção pela facilidade da pronúncia.
A proposta do Façade é encapsular um procedimento complexo, que geralmente envolve uma biblioteca de classes, e disponibilizar ao cliente apenas um método simplificado para executá-lo. Vale ressaltar que quando mencionamos “cliente”, no contexto de Design Patterns, nos referimos à classe que consumirá o padrão, e não ao usuário que utiliza o sistema, ok?
Pois bem, o Façade não recebe este nome por acaso. A tradução, “fachada”, é uma analogia para indicar o que o cliente pode acessar externamente, poupando-o de conhecer detalhes complexos internos.
Analogia
Tome, como exemplo, um televisor. Do lado de fora, como clientes, apenas vemos a fachada da TV (composta por uma tela e vários botões) e não conhecemos o que existe do lado de dentro. Para ter acesso à s suas operações internas, usamos o botão de ligar. Ao pressioná-lo, aguardamos que a TV seja acionada e exiba as imagens adequadamente. O modo como a inicialização é feita – ligar os componentes, sintonizar no último canal visto, carregar as configurações de imagem e volume, etc. – não é do nosso conhecimento, já que envolve uma série de operações. O Façade, neste caso, é o botão de liga/desliga, que atua como intermediário entre o cliente (telespectador) e o sistema técnico da TV. Podemos afirmar, então, que o Façade facilita a utilização de um objeto, transformando operações internas complexas em chamadas únicas e simples.
Traduzindo a analogia em código, o Façade teria a seguinte codificação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure TFacade.LigarTV; var Componentes: TComponentes; Sintonizador: TSintonizador; Configuracao: TConfiguracao; begin Componentes := TComponentes.Create; Sintonizador: TSintonizador.Create; Configuracao: TConfiguracao.Create; try Componentes.LigarComponentes; Sintonizador.CarregarListaDeCanais; Sintonizador.AcessarUltimoCanal; Configuracao.AplicarVolume; Configuracao.AplicarModoImagem; finally FreeAndNil(Componentes); FreeAndNil(Sintonizador); FreeAndNil(Configuracao); end; end; |
O cliente, por sua vez, teria apenas essa chamada:
1 2 3 4 5 6 7 8 9 10 |
var Facade: TFacade; begin Facade := TFacade.Create; try Facade.LigarTV; finally FreeAndNil(Facade); end; end; |
Acho que já deu para captar a mensagem, não é? 🙂
No contexto do Façade, as classes que realizam essas operações complexas são chamadas de Subsystem Classes. Recebem este nome por formar um “subsistema” dentro da aplicação para executar uma rotina de negócio especÃfica. Lembram-se do artigo sobre Separation of Concerns? O Façade constitui um “conceito” dentro do sistema, já que executa ações de forma desacoplada, geralmente envolvendo uma regra de negócio especial.
Viram como é algo relativamente comum? Tenho certeza que vocês, em algumas situações, já criaram Façades involuntariamente, haha.
Exemplo de codificação do Façade
Pessoal, vou abrir o jogo. A parte mais difÃcil de escrever os artigos sobre Design Patterns é pensar em bons exemplos do “mundo real” para apresentar a vantagem da empregabilidade de cada um. Sempre procuro encontrar exemplos simples e básicos, mas, ao mesmo tempo, suficientes para demonstrar os padrões em produção.
Dessa vez, pensei em um cenário de cálculo de valor de venda a partir da cotação do Dólar do dia atual. A ideia é simples: para evitar que o cliente faça a consulta da cotação do Dólar, aplicação de descontos e aplicação de margem de venda (que pode ser considerado um procedimento complexo), criaremos um Façade que disponibilizará um único método – CalcularValorDeVenda
– responsável por realizar todo este processo. Em outras palavras, faremos um wrapper do cálculo do valor de venda.
Para que este exemplo seja factÃvel, consumiremos um WebService do Banco Central do Brasil para consulta do valor do Dólar no dia atual, disponÃvel no endereço WSDL abaixo:
https://www3.bcb.gov.br/sgspub/JSP/sgsgeral/FachadaWSSGS.wsdl
Obervação: viram que o nome do WSDL começa com “Fachada”? Pois é, esse WebService é um Façade. 🙂
Caso você decida acompanhar o artigo, utilize o WSDL Importer do Delphi para criar a biblioteca de funções disponibilizadas pelo WebService.
Subsystem Classes
A primeira etapa é criar as Subsystem Classes, ou seja, as classes que fazem parte do subsistema. No nosso exemplo, teremos 3:
- A classe
TSubSystemCotacaoDolar
para buscar a cotação do Dólar no dia atual:
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 |
type { Subsystem } TSubsystemCotacaoDolar = class public function ConsultarCotacaoDolar: real; end; implementation uses SysUtils, uWSDL_BCB, SOAPHTTPClient, Windows; { TSubsystemCotacaoDolar } function TSubsystemCotacaoDolar.ConsultarCotacaoDolar: real; var WebServiceCotacao: FachadaWSSGS; HTTPRIO: THTTPRIO; FormatSettings : TFormatSettings; begin // cria uma instância da classe THTTPRIO HTTPRIO := THTTPRIO.Create(nil); // obtém uma instância do WSDL WebServiceCotacao := GetFachadaWSSGS(True, '', HTTPRIO); // customiza o separador de decimais para evitar erro de conversão FormatSettings.DecimalSeparator := '.'; // invoca o WebService para buscar a cotação do Dólar do dia result := StrToFloat(WebServiceCotacao.getUltimoValorVO(1).ultimoValor.sValor, FormatSettings); end; |
- A classe
TSubsystemClasseLoja
, encarregada de aplicar o desconto e a margem de venda:
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 38 39 40 |
type { Subsystem } TSubsystemCalculoLoja = class private function AplicarDesconto(const Fidelidade: integer; const Preco: real): real; function AplicarMargemVenda(const Preco: real): real; public function CalcularPrecoFinal(const Fidelidade: integer; const Preco: real): real; end; implementation { TSubsystemCalculoLoja } function TSubsystemCalculoLoja.AplicarDesconto(const Fidelidade: integer; const Preco: real): real; begin // Aplica o desconto conforme a fidelidade do cliente result := Preco; case Fidelidade of 0: result := Preco - (Preco * 0.02); // Nenhuma - 2% de desconto 1: result := Preco - (Preco * 0.06); // Bronze - 6% de desconto 2: result := Preco - (Preco * 0.1); // Prata - 10% de desconto 3: result := Preco - (Preco * 0.18); // Ouro - 18% de desconto end; end; function TSubsystemCalculoLoja.AplicarMargemVenda(const Preco: real): real; begin // Aplica a margem de venda de 35% result := Preco + (Preco * 0.35); end; function TSubsystemCalculoLoja.CalcularPrecoFinal( const Fidelidade: integer; const Preco: real): real; begin // Operação principal do SubSystem: aplica o desconto e a margem de venda result := AplicarDesconto(Fidelidade, Preco); result := AplicarMargemVenda(result); end; |
- Por fim, a classe
TSubsystemHistorico
para armazenar os dados do cálculo em um arquivo texto:
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 38 39 40 41 42 |
type { Subsystem } TSubsystemHistorico = class public procedure RegistrarHistoricoDoCalculo(const Dolar, Preco, ValorVenda: real); end; implementation uses SysUtils; { TSubsystemHistorico } procedure TSubsystemHistorico.RegistrarHistoricoDoCalculo(const Dolar, Preco, ValorVenda: real); var Arquivo: TextFile; PathArquivo: string; Desconto: string; begin // obtém o caminho e nome do arquivo de histórico PathArquivo := ExtractFilePath(ParamStr(0)) + 'Historico.txt'; // associa a variável "Arquivo" com o arquivo "Historico.txt" AssignFile(Arquivo, PathArquivo); if FileExists(PathArquivo) then // se o arquivo já existe, coloca-o em modo de edição Append(Arquivo) else // caso contrário, cria o arquivo Rewrite(Arquivo); // escreve os dados no arquivo "Historico.txt" WriteLn(Arquivo, 'Data..............: ' + FormatDateTime('dd/mm/yyyy', Now)); WriteLn(Arquivo, 'Cotação do Dólar..: ' + FormatFloat('###,###,##0.00', Dolar)); WriteLn(Arquivo, 'Conversão em R$...: ' + FormatFloat('###,###,##0.00', Dolar * Preco)); WriteLn(Arquivo, 'Preço final.......: ' + FormatFloat('###,###,##0.00', ValorVenda)); // fecha o arquivo CloseFile(Arquivo); end; |
Classe Façade
A próxima etapa é… yes, criar o Façade! Observe que a classe terá apenas um método de visibilidade pública, e este utilizará as 3 Subsystem Classes de uma só vez:
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 38 39 40 41 42 43 44 45 46 47 48 49 50 |
type { Façade } TFacade = class public // operação do Façade function CalcularValorDeVenda(const Fidelidade: integer; const Preco: real): real; end; implementation uses SysUtils, uSubsystemCotacaoDolar, uSubsystemCalculoLoja, uSubsystemHistorico; { TFacade } function TFacade.CalcularValorDeVenda(const Fidelidade: integer; const Preco: real): real; var SubsystemCotacaoDolar: TSubsystemCotacaoDolar; SubsystemCalculoLoja: TSubsystemCalculoLoja; SubsystemHistorico: TSubsystemHistorico; CotacaoDolar: real; ValorVenda: real; begin // cria as instâncias dos SubSsystems SubsystemCotacaoDolar := TSubsystemCotacaoDolar.Create; SubsystemCalculoLoja := TSubsystemCalculoLoja.Create; SubsystemHistorico := TSubsystemHistorico.Create; try // utiliza o primeiro Subsystem para consultar a cotação do Dólar CotacaoDolar := SubsystemCotacaoDolar.ConsultarCotacaoDolar; // converte em Reais ValorVenda := Preco * CotacaoDolar; // utiliza o segundo Subsytem para aplicar desconto e margem de venda ValorVenda := SubsystemCalculoLoja.CalcularPrecoFinal(Fidelidade, ValorVenda); // utiliza o terceiro Subsystem para armazenar o histórico do cálculo SubsystemHistorico.RegistrarHistoricoDoCalculo(CotacaoDolar, Preco, ValorVenda); // retorna o valor calculado result := ValorVenda; finally // libera as instâncias da memória FreeAndNil(SubsystemCotacaoDolar); FreeAndNil(SubsystemCalculoLoja); FreeAndNil(SubsystemHistorico); end; end; |
Em ação!
O terceiro e último passo é colocar o Façade em prática, utilizando-o em um Client que, no nosso exemplo, é uma tela simples que possui os dados do cliente e o preço do produto (em Dólares), nos quais serão passados por parâmetros para o Façade:
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 |
var Fidelidade: smallint; Preco: real; Facade: TFacade; ValorVenda: double; begin Fidelidade := ClientDataSetClientes.FieldByName('Fidelidade').AsInteger; Preco := ClientDataSetProdutos.FieldByName('Preco').AsFloat; // cria uma instância do Façade Facade := TFacade.Create; try // chama o método do Façade que, por sua vez, // executa as operações de todos os Subsystems ValorVenda := Facade.CalcularValorDeVenda(Fidelidade, Preco); // exibe uma mensagem com o valor de venda calculado ShowMessage(FormatFloat('###,###,##0.00', ValorVenda)); // carrega o log da operação (gerado por um dos Subsystems) Memo.Lines.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'Historico.txt'); finally // libera o objeto da memória FreeAndNil(Facade); end; end; |
Que moleza para o Client, hein? Apenas solicita o cálculo do valor de venda, sem conhecer a “trabalheira” que ocorre por trás. 🙂
Conclusão
Na equipe em que faço parte, utilizamos o Façade sempre que necessário, principalmente pelo motivo que, no módulo em que trabalhamos, há várias regras de negócio especÃficas que exigem processamentos complexos, como cálculos periódicos, atualização de valores monetários e emissão de relatórios extensos. Com este padrão de projeto, mantemos estes subsistemas encapsulados, acessando-os somente pelo método que é exposto. Não precisamos nos preocupar com a complexidade interna.
Leitores, disponibilizei este projeto de exemplo no link abaixo, com breves modificações. Ao executá-lo no seu Delphi, observe que adicionei dois componentes TDBGrid
: um para os dados dos clientes e outro para os dados dos produtos. Intercale a seleção de registros entre eles e clique em “Calcular Valor de Venda” para obter diferentes resultados.
Ah, e sabe por que usei um prisma na imagem do artigo?
É uma analogia bem abstrata. Considere que o feixe de luz branca é a requisição do Client, o prisma é o Façade, e as luzes coloridas são as Subsystem Classes. Será que prismas dispersivos são Façades?
Um grande abraço e até a próxima!
André excelente explicação.
No meu ultimo projeto fiz uma implementação muito parecido, porém usei interface e composição de objetos.
como eu precisava trabalhar com um fluxo enorme de dados e gerenciar algumas regras de negócios foi a melhor solução.
Para se der uma ideia, para executar o método realizar uma unica chamada na camada de visão, passando um objeto e alguns parâmetros que o resto ele se virá.
Alias a implementação foi a emissão de NFSe para maringá, envio de 5 mil notas de um cliente, claro que uma nota por vez rsrsrs.
Ficou algo bem bacana e de fácil manutenção, pois o programador que for dar manutenção não precisa se preocupar com a regra de negócio rsrsrs.
Parabéns
Olá, André! Que legal receber um comentário seu! 🙂
Obrigado pelo depoimento. Eu diria que podemos considerar a sua implementação como um Façade, já que simplifica um processo complexo relacionado à regra de negócio. Na verdade, é um Façade mais “elaborado” em função da utilização de interfaces e composição de objetos, que são dois conceitos relevantes da Orientação a Objetos!
Grande abraço!
Parabéns André ficou top… Realmente, agente em algum momento acabamos fazendo um “façade” involuntariamente hhahah… Fiz isso a pouco tempo.
Parabéns
Muito obrigado, Daniel!
Rapaz, não só o Façade, mas eventualmente também codificamos outros Design Patterns e não sabemos! rsrs
Abração!