[Delphi] Design Patterns – Template Method

[Delphi] Design Patterns - Template Method

Saudações, programadores!
Estou certo de que, em algum momento (ou vários deles), você já trabalhou com herança de classes no desenvolvimento de software. Trata-se de um recurso valiosíssimo da Orientação a Objetos que contribui para uma arquitetura de fácil manutenção através do reaproveitamento de código. O padrão de projeto Template Method está intimamente associado a este conceito. Confira!

 

Nós, programadores, utilizamos herança com bastante frequência, principalmente quando uma regra de negócio é decomposta em várias regras específicas, mas parte dela é mantida em comum, compartilhada. Quando isso ocorre, geralmente criamos uma classe generalizada (ou popularmente chamada de classe base, classe pai ou superbase) e derivamos novas classes a partir dela, dando origem às classes especializadas (também conhecidas como subclasses ou classes filhas). Optamos por essa abordagem pelo motivo de que algumas classes possuem comportamentos similares e, ao invés de copiar e colar o código, o reaproveitamos.

Um caso clássico que podemos citar sobre herança envolve a recuperação de dados de uma tabela. Considere, por exemplo, em um software contábil, um método que traz os dados de documentos executando os seguintes passos:

function ConsultarDados: TObjectList<T>;
begin
  ConectarBancoDeDados;
  result := ExecutarConsulta;
  DesconectarBancoDeDados;
end;

 

Para recuperar dados de outra tabela, como a de impostos, teríamos a mesma sequência de passos, com exceção de que a segunda linha retornaria os dados de impostos ao invés de documentos. Observe, então, que temos dois comportamentos em comum: conectar e desconectar o banco de dados (primeira e terceira linhas). O método ExecutarConsulta, na segunda linha, poderia ser implementado somente pelas subclasses. Cada uma executaria um tipo de consulta, enquanto o controle da conexão seria feito na classe base, já que é idêntico para qualquer consulta.

Pois bem, neste exemplo, o método ConsultarDados (que executa os três passos) pode ser considerado um Template Method! Recebe essa definição porque consiste em um método que estabelece uma sequência de passos, porém, alguns deles são “delegados” para as subclasses em tempo de execução. Em outras palavras, as subclasses ganham a liberdade de alterar o comportamento de alguns trechos do método, contanto que a sequência de passos sempre permaneça a mesma.

Para concluir o exemplo, o método ExecutarConsulta seria declarado como abstrato na classe base e implementado nas subclasses, utilizando as palavras reservadas virtual, abstract e override do Delphi. Para os desenvolvedores que já estão acostumados a trabalhar com herança, esse conceito fica bem fácil de compreender.
Embora o padrão de projeto proponha uma arquitetura de classes com base em heranças, a operação principal ocorre em um único método, que compreende uma sequência prevista de passos. É ali que tudo ocorre.

 

André, o objetivo desse padrão de projeto parece o mesmo do Strategy…
Sim, o Template Method realmente traz alguma semelhança com o Strategy, mas há duas diferenças importantes que os distinguem:
1) Com o Strategy, implementamos algoritmos que geram resultados semelhantes. Já com o Template Method, os resultados são sempre diferentes;
2) No Template Method, há uma classe base que executa ações compartilhadas por todas as subclasses, diferente do Strategy, no qual trabalha-se com Interfaces e cada classe possui um algoritmo específico.

 

Dito isso, avançaremos para um exemplo prático bem interessante. Criaremos uma aplicação para consultar repositórios e usuários do GitHub através de uma API REST disponibilizada pelo próprio serviço. Conforme a documentação da API, as rotas são:

  • Repositórios: https://api.github.com/search/repositories?q={busca}
  • Usuários: https://api.github.com/search/users?q={busca}

 

No artigo anterior, utilizamos o IdHTTP para receber o retorno de uma requisição GET. Dessa vez, faremos diferente. O Delphi fornece um conjunto de componentes exclusivo para esse padrão de comunicação, encontrados na paleta REST Client. Utilizaremos os componentes TRESTClient para configurar a URL base, TRESTRequest para enviar a requisição e TRESTResponse para receber a resposta em JSON.
O procedimento consiste em quatro passos: inicializar os objetos, enviar a requisição, processar o retorno e liberar os objetos da memória. Essa sequência de etapas será executada em um método chamado ConsultarDadosGitHub que, como já podemos induzir, é o nosso Template Method!

function TAbstractClass.ConsultarDadosGitHub: olevariant;
begin
  InicializarObjetos;
  EnviarRequisicao;
  result := ProcessarRetorno; // este método será abstrato
  LiberarObjetos;
end;

 

De todos os Design Patterns já apresentados até o momento, acredito que o Template Method é o que possui o menor número de elementos. Precisamos codificar apenas a Abstract Class, que declara os métodos abstratos, e as Concrete Classes, que implementam os métodos abstratos.

Iniciaremos, então, pela Abstract Class. Nela, teremos a declaração do método ConsultarDadosGitHub e a maior parte dos passos já implementada. A inicialização de objetos, envio da requisição e liberação dos objetos da memória serão codificados aqui.
O destaque da Abstract Class está na existência de métodos abstratos que fazem parte da sequência de passos. No código a seguir, essa característica é dada ao método ProcessarRetorno. Cada Concrete Class será responsável por implementar este método para processar o JSON de diferentes formas.
Para facilitar a compreensão, procurei adicionar comentários na maioria das linhas:

uses
  System.JSON, REST.Client, IPPeerClient;
 
type
  TAbstractClass = class
  private
    // Objetos necessário para a comunicação com a API
    FRESTClient: TRESTClient;
    FRESTRequest: TRESTRequest;
    FRESTResponse: TRESTResponse;
 
    // Métodos internos da classe
    procedure InicializarObjetos;
    procedure EnviarRequisicao;
    procedure LiberarObjetos;
  protected
    // Variável para armazenar o parâmetro de consulta que será enviado na URL
    FParametros: string;
    // Objeto para receber o JSON de retorno
    FJSON: TJSONObject;
 
    // Método que será implementado nas subclasses
    function ProcessarRetorno: olevariant; virtual; abstract;
  public
    // Template Method
    function ConsultarDadosGitHub: olevariant;
  end;
 
implementation
 
uses
  System.SysUtils, REST.Types;
 
{ TAbstractClass }
 
procedure TAbstractClass.EnviarRequisicao;
begin
  // Configura os parâmetros da requisição
  FRESTRequest.Resource := FParametros;
 
  // Executa a requisição
  FRESTRequest.Execute;
 
  // Recebe o retorno em JSON e o atribui ao objeto FJSON
  FJSON.Parse(TEncoding.ASCII.GetBytes(FRESTResponse.JSONValue.ToString), 0);
end;
 
procedure TAbstractClass.InicializarObjetos;
begin
  // Cria o objeto da classe TRESTClient
  FRESTClient := TRESTClient.Create('https://api.github.com/search/');
 
  // Cria o objeto da classe TRESTResponse
  FRESTResponse := TRESTResponse.Create(nil);
 
  // Cria e configura o objeto da classe TRESTRequest
  FRESTRequest := TRESTRequest.Create(nil);
  FRESTRequest.Client := FRESTClient;
  FRESTRequest.Response := FRESTResponse;
  FRESTRequest.Method := rmGET;
 
  // Cria o objeto para manipular o JSON
  FJSON := TJSONObject.Create;
end;
 
procedure TAbstractClass.LiberarObjetos;
begin
  // Libera os objetos da memória
  FRESTRequest.Free;
  FRESTResponse.Free;
  FRESTClient.Free;
end;
 
function TAbstractClass.ConsultarDadosGitHub: olevariant;
begin
  // Este é o Template Method
  // O comportamento do método "ProcessarRetorno" será definido em tempo de execução
 
  InicializarObjetos;
  EnviarRequisicao;
  result := ProcessarRetorno;
  LiberarObjetos;
end;

 

Em seguida, codificaremos a Concrete Class referente à consulta de repositórios, encarregada de implementar o método abstrato ProcessarRetorno para converter o JSON em registros de um DataSet. Além disso, no construtor, atribuímos o resource (parâmetro de busca) à variável FParametro, que será consumida pelo método EnviarRequisicao da Abstract Class.
Aproveitando o ensejo, observem a simplicidade na leitura do array JSON utilizando as classes do namespace System.JSON do Delphi.

uses
  Pattern.AbstractClass;
 
type
  TConcreteClassRepositories = class(TAbstractClass)
  protected
    function ProcessarRetorno: olevariant; override;
  public
    constructor Create(const Parametro: string);
  end;
 
implementation
 
uses
  System.SysUtils, System.JSON, Data.DB, Datasnap.DBClient;
 
{ TConcreteClassRepositories }
 
constructor TConcreteClassRepositories.Create(const Parametro: string);
begin
  // Configura os parâmetros referente à consulta de repositórios
  FParametros := 'repositories?q=%s' + Parametro;
end;
 
function TConcreteClassRepositories.ProcessarRetorno: olevariant;
var
  DataSetRetorno: TClientDataSet;
  JSONValue: TJSONValue;
  JSONObject: TJSONObject;
begin
  // Cria um DataSet para tabular os dados consultados
  DataSetRetorno := TClientDataSet.Create(nil);
  try
    // Define as colunas
    DataSetRetorno.FieldDefs.Add('ID', ftInteger);
    DataSetRetorno.FieldDefs.Add('Nome', ftString, 40);
    DataSetRetorno.FieldDefs.Add('Linguagem', ftString, 15);
    DataSetRetorno.FieldDefs.Add('Observadores', ftInteger);
    DataSetRetorno.CreateDataSet;
 
    // Percorre o JSON, lendo os valores das chaves
    for JSONValue in FJSON.GetValue('items') as TJSONArray do
    begin
      JSONObject := JSONValue as TJSONObject;
 
      DataSetRetorno.AppendRecord([
        JSONObject.GetValue('id').Value,
        JSONObject.GetValue('full_name').Value,
        JSONObject.GetValue('language').Value,
        JSONObject.GetValue('watchers').Value
      ]);
    end;
 
    // Devolve os dados tabulados
    result := DataSetRetorno.Data;
  finally
    DataSetRetorno.Free;
  end;
end;

 

A Concrete Class para consulta de usuários segue o mesmo padrão. Implementa o método abstrato ProcessarRetorno e define o resource no construtor:

uses
  Pattern.AbstractClass;
 
type
  TConcreteClassUsers = class(TAbstractClass)
  protected
    function ProcessarRetorno: olevariant; override;
  public
    constructor Create(const Parametro: string);
  end;
 
implementation
 
uses
  System.SysUtils, System.JSON, Data.DB, Datasnap.DBClient;
 
{ TConcreteClassUsers }
 
constructor TConcreteClassUsers.Create(const Parametro: string);
begin
  // Configura os parâmetros referente à consulta de usuários
  FParametros := 'users?q=%s' + Parametro;
end;
 
function TConcreteClassUsers.ProcessarRetorno: olevariant;
var
  DataSetRetorno: TClientDataSet;
  JSONValue: TJSONValue;
  JSONObject: TJSONObject;
begin
  // Cria um DataSet para tabular os dados consultados
  DataSetRetorno := TClientDataSet.Create(nil);
  try
    // Define as colunas
    DataSetRetorno.FieldDefs.Add('ID', ftInteger);
    DataSetRetorno.FieldDefs.Add('Login', ftString, 25);
    DataSetRetorno.FieldDefs.Add('URL', ftString, 40);
    DataSetRetorno.FieldDefs.Add('Score', ftFloat);
    DataSetRetorno.CreateDataSet;
 
    // Percorre o JSON, lendo os valores das chaves
    for JSONValue in FJSON.GetValue('items') as TJSONArray do
    begin
      JSONObject := JSONValue as TJSONObject;
 
      DataSetRetorno.AppendRecord([
        JSONObject.GetValue('id').Value,
        JSONObject.GetValue('login').Value,
        JSONObject.GetValue('html_url').Value,
        JSONObject.GetValue('score').Value
      ]);
    end;
 
    // Devolve os dados tabulados
    result := DataSetRetorno.Data;
  finally
    DataSetRetorno.Free;
  end;
end;

 

Para finalizar, elaborei um formulário bem simples para atuar como Client:

Formulário de exemplo para uso do Template Method

 

No botão “Consultar”, basta chamar o Template Method (ConsultarDadosGitHub) da Concrete Class desejada. Como este exemplo é didático, não adicionei tratamento de exceções, mas eles são importantes!

var
  ConcreteClass: TAbstractClass;
begin
  ConcreteClass := nil;
 
  // Obtém a instância de uma Concrete Class de acordo com o tipo selecionado
  case RadioGroupTipo.ItemIndex of
    0: ConcreteClass := TConcreteClassRepositories.Create(EditConsulta.Text);
    1: ConcreteClass := TConcreteClassUsers.Create(EditConsulta.Text);
  end;
 
  try
    // Executa o Template Method
    ClientDataSet.Data := ConcreteClass.ConsultarDadosGitHub;
  finally
    ConcreteClass.Free;
  end;
end;

Exemplo de uso do Template Method

Feito, pessoal!

 

Ao analisar este exemplo, a proposta do Template Method pode nos remeter à uma única necessidade: conectar e desconectar de um serviço ou um banco de dados. No entanto, estes são apenas cenários tradicionais. O Template Method é adequado para qualquer situação em que partes de um algoritmo devem ser definidos em tempo de execução como, por exemplo, a manipulação de um arquivo:

procedure AbrirArquivo;
procedure EscreverDados; virtual; abstract; // implementado pelas subclasses
procedure FecharArquivo;

 

É importante destacar também que os métodos abstratos não devem necessariamente aparecer no meio do algoritmo, como no código anterior. Não existe essa uma regra. Os métodos abstratos podem surgir em qualquer posição, como intercalados:

ConfigurarFormatoExportacao; virtual; abstract;
ExportarDados;
ConfigurarEmail; virtual; abstract;
EnviarEmailComArquivo;

 

O essencial, na verdade, é a existência um método que execute uma sequência de passos, mas alguns deles são definidos em tempo de execução por classes derivadas. Aos poucos, você notará que a implementação do Template Method é tão comum quanto parece. 🙂

O projeto de exemplo deste artigo está disponível para download no link abaixo. Neste projeto, adicionei mais alguns parâmetros na rota de busca para trazer 100 resultados, já que, por padrão, a API retorna somente 30.

Exemplo de Template Method com Delphi

O exemplo também está disponível no GitHub (olhem só, um consumidor do GitHub no GitHub!):

https://github.com/AndreLuisCelestino/Delphi-DesignPatterns/tree/master/TemplateMethod-AndreCelestino

 

Grande abraço, pessoal!
Até breve.


 

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

2 comentários

  1. Grande André!

    Mais um artigo cinco estrelas sobre design patterns. Excelente! Não por acaso vc é um dos Mestres do Código na DB1. Parabéns e sucesso! 😀

    Abração!

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.