[Delphi] Design Patterns – Prototype

[Delphi] Design Patterns - Prototype

Olá, leitores!!! Ainda lembram de mim? 🙂
Depois de um graaaande tempo de recesso, volto à ativa para continuar a série de artigos sobre Design Patterns. Peço desculpas pela ausência e agora prometo que estarei sempre por perto!
Cutting to the chase, o próximo padrão de projeto da pauta é o Prototype. Mesmo que você já o conheça, gostaria de convidá-lo para acompanhar este artigo. Tenho algumas observações e detalhes para fazer sobre este padrão de projeto.

 

Assim como aconteceu com o meu estudo anterior, sobre o Factory Method, o padrão Prototype também me deixou conturbado. A princípio, entendi a mecânica do padrão, mas ainda não conseguia visualizá-lo em um ambiente real. Como você sabem, o maior intuito dessa série de artigos é fugir dos exemplos tradicionais encontrados da internet e apresentar cenários mais próximos da realidade, portanto, eu queria, a todo custo, encontrar um sentido concreto para o Prototype.

Li vários artigos e também discuti o padrão com os colegas de trabalho (algo que gosto muito de fazer) e, aos poucos, notei que o Prototype é tão comum quanto parece. Em breves palavras, o objetivo principal deste padrão é copiar o estado e propriedades de um objeto já existe para um novo objeto.

 

Você quis dizer… clonar?
Exato! O Prototype é geralmente utilizado para criar um clone de um objeto, de forma que este possa receber novos valores para os atributos sem impactar o objeto original.
Os programadores Delphi devem conhecer, por exemplo, o método CloneCursor da classe TClientDataSet:

ClientDataSet2.CloneCursor(ClientDataSet1, True);

Este método clona o TClientDataSet indicado no parâmetro, mantendo (opcionalmente) os filtros, índices e outras configurações, atribuindo-os a um novo objeto (chamador do método). Ora, portanto, poderíamos considerar que há um Prototype nessa operação. Claro, não significa necessariamente que este método foi implementado com este padrão, mas seria um caso clássico.

 

Para qual finalidade eu utilizaria este Design Pattern?
Posso mencionar algumas delas. Primeiro, abra o Google Chrome e veja os menus que são exibidos ao clicar com o botão direito em uma aba. Um deles é o “Duplicar” que, obviamente, cria uma cópia da aba atual:

Menu "Duplicar" do Google Chrome

 

Porém, observe que o navegador não só duplica a aba, como também copia outras propriedades, como a posição da página. Se o usuário estiver no final da página e acessar essa opção do menu, a aba copiada também exibirá a página no mesmo ponto. Poderíamos afirmar, então, que essa ação clona a aba atual. O Prototype cairia perfeitamente nessa funcionalidade. Talvez até tenha sido desenvolvida com este padrão.
Outra aplicação do Prototype são as funcionalidades “temporárias”. Imagine um sistema que possua uma funcionalidade de troca de temas visuais, mas que permita que o usuário “experimente” o tema, de modo temporário, antes de efetivamente aplicá-lo. Ao selecionar o tema, o desenvolvedor pode utilizar um Prototype para clonar os aspectos visuais e aplicar o novo tema temporariamente. Se o usuário confirmar a utilização deste tema, o objeto clonado sobrescreve o objeto original, passando a exibir o novo tema permanentemente. Caso contrário, o clone é descartado e o objeto (tema) original é restaurado. Interessante, não?

 

Este artigo também terá uma parte prática?
Claro! Para exemplificar o uso do Prototype, pensei em um gerenciador de reuniões. Imagino que você já deve ter pensando: “Acho que utilizaremos o padrão para clonar reuniões existentes, evitando que o usuário tenha que digitar tudo novamente”. É isso mesmo! 🙂
O propósito do conjunto de códigos a seguir é permitir que o usuário clone uma reunião (que é um objeto existente), criando uma nova reunião (um novo objeto), copiando todos os valores dos seus atributos.
Primeiramente, a implementação da classe de reunião:

type
  TReuniao = class
  private
    FNome: string;
    FData: TDate;
    FHora: TTime;
    FCategoria: TColor;
    FParticipantes: string;
  public
    constructor Create;
 
    // método principal do Prototype
    function Clonar: TReuniao;
 
    property Nome: string read FNome write FNome;
    property Data: TDate read FData write FData;
    property Hora: TTime read FHora write FHora;
    property Categoria: TColor read FCategoria write FCategoria;
    property Participantes: string read FParticipantes write FParticipantes;
  end;

Observe que declaramos uma função chamada “Clonar”. A implementação dessa função é o trabalho principal do Prototype:

function TReuniao.Clonar: TReuniao;
var
  NovaReuniao: TReuniao;
begin
  // cria um novo objeto
  NovaReuniao := TReuniao.Create;
 
  // copia as propriedades do objeto atual,
  // atribuindo-as ao novo objeto criado
  NovaReuniao.Nome := Self.Nome;
  NovaReuniao.Data := Self.Data;
  NovaReuniao.Hora := Self.Hora;
  NovaReuniao.Categoria := Self.Categoria;
  NovaReuniao.Participantes := Self.Participantes;
 
  result := NovaReuniao;
end;

 

Na nossa aplicação cliente, teremos uma lista ou tabela de reuniões. Cada uma delas será um objeto e, para armazenar todas elas, trabalharemos com uma lista de objetos (TObjectList). No exemplo, chamarei essa lista de “ListaReunioes”:

ListaReunioes: TObjectList;

Sendo assim, se o usuário optar por duplicar (clonar) uma reunião, faríamos os seguintes passos:

  • Selecionar o objeto da reunião na lista de objetos;
  • Clonar a reunião;
  • Adicionar o clone na lista de objetos, para que esse também possa ser clonado posteriormente.
var
  ReuniaoSelecionada: TReuniao;
  ReuniaoClone: TReuniao;
  Indice: integer;
begin
  // a implementação da busca do índice depende do componente utilizado para listar as reuniões
  // ListBox, DBGrid, StringGrid, etc...
  Indice := ObterIndiceDaReuniao;
 
  // seleciona a reunião na lista de objetos conforme o índice
  // e atribui à variável "ReuniaoSelecionada"
  ReuniaoSelecionada := ListaReunioes[Indice] as TReuniao;
 
  // comando para clonar a reunião
  ReuniaoClone := ReuniaoSelecionada.Clonar;
 
  // adiciona o clone na lista de objetos
  ListaReunioes.Add(ReuniaoClone);
end;

 

Perfeito! Ao clonar a reunião, todas as propriedades (nome, data, hora, categoria e participantes) são copiados para o novo objeto, poupando o tempo que o usuário teria em informá-las novamente.
O exemplo é básico, apresentando uma classe que possui apenas cinco atributos. Pense, agora, em classes maiores, com vários atributos e estados. O benefício de uso do Prototype seria evidente. 

Ficou complicado de entender? Sem problemas! Baixe o exemplo completo no link abaixo e confira cada linha de código para compreender melhor o funcionamento.
Obs: no projeto de exemplo, codifiquei a parte visual. A lista de reuniões é exibida em uma TListBox e no rodapé da aplicação há dois botões. O primeiro (Nova) cria uma nova reunião, enquanto o segundo (Duplicar) clona a reunião atualmente selecionada. Observe atentamente a diferença na implementação de cada um.

Exemplo de Prototype com Delphi

 

Qualquer dúvida, estarei à disposição, pessoal!
Um grande abraço e até o próximo artigo!


 

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

11 comentários

  1. Olá, muito bom seu artigo, meus parabéns!!! 🙂
    Apenas para colaborar, no Delphi a classe Tpersistent apresenta o método virtual Assign que possui essa finalidade de copiar/clonar um objeto. A implementação do método pode ser observada nas classes que herdam de Tpersistent, como por exemplo Tstrings e Tcollection.

    1. Olá, Rudinei!
      Agradeço pela colaboração! Eu, sinceramente, não sabia que a classe TPersistent permitia essa operação. Vou estudar as heranças dela.
      Bom, talvez podemos afirmar, então, que a TPersistent fornece um “mecanismo de Prototype”, rsrs.

      Obrigado! Abraço!

  2. Muito bom artigo, André!
    Apenas uma curiosidade: Caso fosse necessário implementar uma função para localizar uma reunião na lista de objetos, usando um critério escolhido pelo usuário, como poderia fazê-lo?

    1. Opa, obrigado, Marcos!

      A lista de objetos que usei no exemplo (TObjectList) apenas permite a localização de objetos por índice. Por exemplo:

      oReuniaoSelecionada := (FoListaReunioes[INDICE]) as TReuniao;

      Porém, nem sempre o índice é um critério de busca satisfatório, ou melhor, confiável. Eventualmente os índices podem ficar fora de ordem, ainda mais falando de um sistema como esse, de gerenciamento de reuniões.
      Portanto, uma solução plausível é utilizar os atributos da classe para encontrar o objeto. Vamos supor que o usuário deseja encontrar uma reunião pelo nome. Podemos usar esse atributo como parâmetro de busca e percorrer a lista de objetos, comparando o nome em cada um deles:

      var
        sNome: string;
        nContador: integer;
      begin
        // critério de busca
        sNome := 'Reunião de Planejamento';
      
        // loop na lista de objetos
        for nContador := 0 to Pred(FoListaReunioes.Count) do
        begin
          // compara o critério de busca com a reunião do índice atual
          if (FoListaReunioes[nContador] as TReuniao).Nome = sNome then
          begin
            ShowMessage('Reunião encontrada.');
      
            // retorna o objeto da reunião
            result := FoListaReunioes[nContador] as TReuniao;
          end;
        end;
      end;

      Legal, né?
      Espero ter respondido!

      Abraço!

  3. Excelente artigo André, já estava com medo de você não voltar mais a escrever. Que bom que voltou, estou sempre aqui acompanhando, obrigado pelo seu trabalho e continue assim.

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.