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