Olá, leitores! 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.
Introdução
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 é mais comum do que parece. Em breves palavras, o objetivo principal deste padrão é copiar o estado e propriedades de um objeto já existe para um novo objeto.
Em outras palavras, 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.
Muitos programadores Delphi devem conhecer, por exemplo, o método CloneCursor
da classe TClientDataSet
:
1 |
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). Oras, 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 exemplo clássico.
Exemplos que provavelmente usam o Prototype
Quando você abrir o Google Chrome, 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:
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?
Exemplo de codificação do Prototype
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 preencher 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
:
1 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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.
Conclusão
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.
Ah, nesse 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.
Qualquer dúvida, estarei à disposição, pessoal!
Um grande abraço e até o próximo artigo!
Excelente artigo irmão! Parabens
Oooopa! Muito obrigado, Reginaldo!
Faz tempo que não conversamos. Precisamos marcar um bate-papo. 🙂
Abraço!
Parabéns. Fantástico o Dicas e Exemplos do site. Obrigada, quebrou o maior galho.
Opa, que bom ler um comentário como o seu, Cleusa!
Muito obrigado!
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.
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!
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?
Opa, obrigado, Marcos!
A lista de objetos que usei no exemplo (TObjectList) apenas permite a localização de objetos por Ãndice. Por exemplo:
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:
Legal, né?
Espero ter respondido!
Abraço!
Obrigado pela resposta.
Valeu
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.
Opa, estou de volta, sim, xará!
Fico muito agradecido por estar acompanhado o blog!
Ontem postei o novo artigo sobre Singleton.
Abraço!
Bom dia! Mais um excelente artigo explicado em casos reais onde facilita não só o entendimento como sua aplicabilidade.
Uma dúvida seria interessante fazer Rtti ao invés de ficar copiando propriedade por propriedade da classe origem para a classe Clone, pois a classe poderia ter muitos atributos e caso nascesse outro essa parte precisaria ser alterada o que acha?
Grande abraços e parabéns pelo excelente trabalho.
Olá, Ronaldo.
Excelente comentário! Sim, com RTTI o código fica bem menor e mais “confiável” já que, quando uma nova propriedade for adicionada, não será necessário adicioná-la no método de clone.
Na verdade, se eu fosse implementar o Prototype em um projeto real, com certeza utilizaria RTTI 😀
Obrigado pelo feedback, Ronaldo. Abraço!