[Delphi] Design Patterns – Façade

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!

 

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

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:

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!

Galera, eu vou abrir o jogo. A parte mais difícil de escrever os artigos sobre Design Patterns é pensar em bons exemplos do “mundo real” para evidenciar 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

Caso você decida acompanhar o artigo, utilize o WSDL Importer do Delphi para criar a biblioteca de funções disponibilizadas pelo WebService.

 

Por onde começar?
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:
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:
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:
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;

 

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:

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;

 

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:

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. 🙂
Na DB1 Global Software, 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 elaborados. 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.

Exemplo de Façade com Delphi

 

Ah, por quê você usou 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!


 

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

4 comentários

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

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

  2. Parabéns André ficou top… Realmente, agente em algum momento acabamos fazendo um “façade” involuntariamente hhahah… Fiz isso a pouco tempo.

    Parabéns

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.