Neste exemplo utilizamos a classe TObjectList para lidar com uma coleção de objetos. Essa é uma classe genérica e funciona de forma semelhante à TList, porém ela se encarrega de liberar seus itens da memória quando é destruída. Isso evita o vazamento de memória (memory leak) em nossas aplicações, que ocorre quando itens adicionados à coleção não são liberados.

Para demonstrar o uso desse tipo de coleção criamos uma estrutura orientada a objetos na qual temos duas classes: TVenda e TVendaItem. Além disso temos um formulário para fazer a manipulação dos objetos dentro da nossa coleção. Esses elementos estão organizados em units, da seguinte forma:

  • uVenda: unit contendo a classe TVenda;
  • uVendaItem: unit contendo a classe TVendaItem;
  • uFrmListaObjetos: unit contendo o formulário principal.

Cada uma dessas classes será explicada mais adiante.

Classe TVenda

Nesta classe temos três propriedades: IDVenda (Integer), Data (TDateTime) e ListaVendaItem(TObjectList<TVendaItem>). Além disso, implementamos seu construtor, destrutor e o método AdicionarVendaItem.

Na unit uVenda, o primeiro ponto a ser observado é que referenciamos na seção uses a unit System.Generics.Collections, pois é nela que está declarada a classe TObjectList:

uses
     System.Generics.Collections, System.SysUtils, uVendaItem;

Note que também estamos referenciando a unit uVendaItem, na qual declaramos a classe TVendaItem, e a System.SysUtils, na qual encontra-se a função EncodeDate que será usada mais adiante.

Na sequência temos a definição da classe TVenda, como vemos abaixo:

01 TVenda = class
02  private
03    FIDVenda: Integer;
04    FData: TDateTime;
05    FListaVendaItem: TObjectList<TVendaItem>;
06    { private declarations }
07  protected
08    { protected declarations }
09  public
10    { public declarations }
11    property IDVenda: Integer read FIDVenda write FIDVenda;
12    property Data   : TDateTime read FData write FData;
13    property ListaVendaItem: TObjectList<TVendaItem> read FListaVendaItem
14                                                  write FListaVendaItem;
15    constructor Create;
16    destructor Destroy; override;
17    procedure AdicionarVendaItem(pProduto: String);
18  published
19    { published declarations }
20 end;

Linhas 3 a 5: Atributos privados que representam o id, a data e os itens da venda, respectivamente;

Linhas 11 a 14: Propriedades públicas que encapsulam o acesso aos atributos privados. Note que cada propriedade é responsável por ler e escrever valores em cada um dos atributos privados;

Linhas 15 e 16: Declaração do construtor e do destrutor da classe, respectivamente;

Linha 17: Declaração do método responsável por adicionar um novo item a essa venda.

O método AdicionarVendaItem é responsável por instanciar um objeto do tipo TVendaItem dentro da propriedade FListaVendaItem e preencher suas propriedades de forma estática, da seguinte forma:

01 procedure TVenda.AdicionarVendaItem(pProduto: String);
02 var
03  I: Integer;
04 begin
05  FListaVendaItem.Add(TVendaItem.Create);
06  I := FListaVendaItem.Count -1;
07  FListaVendaItem[I].IDVendaItem := I;
08  FListaVendaItem[I].IDVenda     := FIDVenda;
09  FListaVendaItem[I].Produto     := pProduto;
10 end;

Linha 3: Aqui declaramos uma variável que será responsável por controlar o índice do item que está sendo adicionado a coleção;

Linha 5: Utilizamos o método Add da coleção, passando para ele um novo objeto do tipo TVendaItem;

Linha 6: Nesta linha obtemos o índice do último item da coleção, ou seja, aquele que acabou de ser adicionado. Precisamos desse índice para referenciar esse objeto nas próximas linhas;

Linhas 7 a 9: Preenchemos o item recém-criado.

Mais abaixo, na implementação do método Create da classe TVenda, estamos atribuindo valores iniciais aos fields e também instanciando a nossa coleção de itens:

01 constructor TVenda.Create;
02 begin
03  inherited;
04  FIDVenda        := 0;
05  FData           := EncodeDate(1900,1,1);
06  FListaVendaItem := TObjectList<TVendaItem>.Create;
07 end;

Linha 3: Nesta linha garantimos que será executada uma chamada ao método Create da classe ancestral de TVenda, nesse caso TObject;

Linha 4: Atribuímos o valor 0 ao field FIDVenda;

Linha 5: Utilizando a função EncodeDate setamos um valor padrão para a data da nossa venda;

Linha 6: Neste momento estamos instanciando a nossa coleção de itens, para adicionarmos itens a ela posteriormente.

Por fim, na implementação do método Destroy da classe TVenda precisamos liberar a nossa coleção da memória e ela se encarrega de liberar todos os itens que contém:

01 destructor TVenda.Destroy;
02 begin
03  FreeAndNil(FListaVendaItem);
04  inherited;
05 end;

Linha 3: Nesta linha liberamos a nossa coleção da memória e consequentemente todos os seus itens;

Linha 4: Aqui garantimos que será executada uma chamada ao Destroy da classe ancestral de TVenda, ou seja, TObject.

Classe TVendaItem

A classe TVendaItem possui apenas três propriedades, a fim de simular de forma simplificada os itens e uma venda. Sua definição pode ser vista abaixo:

01 TVendaItem = class
02  private
03    FIDVendaItem: Integer;
04    FIDVenda: Integer;
05    FProduto: String;
06    { private declarations }
07  protected
08    { protected declarations }
09  public
10    { public declarations }
11    property IDVendaItem: Integer read FIDVendaItem write FIDVendaItem;
12    property IDVenda: Integer read FIDVenda write FIDVenda;
13    property Produto: string read FProduto write FProduto;
14  published
15    { published declarations }
16 end;

Linhas 3 a 5: atributos privados que representam o id do item, o id da venda e o nome do produto, respectivamente;

Linhas 11 a 13: propriedades públicas que encapsulam o acesso aos atributos privados. Note que cada propriedade é responsável por ler e escrever valores em cada um dos atributos privados.

Form TFrmListaObjetos

Neste formulário, cuja interface pode ser vista na Figura 1, usamos as classes definidas anteriormente para simular a criação de uma venda, a adição de itens e sua posterior listagem em um controle do tipo TMemo:

Layout do form principal
Figura 1. Layout do form principal

Na seção private desse form declaramos uma variável do tipo TVenda, da seguinte forma:

private
  Venda: TVenda;

Em seguida é necessário instanciar esse objeto, o que foi feito no construtor do form, para garantir que após o form ser criado já poderemos usar esse objeto normalmente:

procedure TFrmListaObjetos.FormCreate(Sender: TObject);
begin
 Venda := TVenda.Create;
end;

A partir daí já podemos preencher essa venda e inserir itens. Para isso utilizamos o evento OnClick do botão “Cadastrar Venda”:

procedure TFrmListaObjetos.BtnCadastrarVendaClick(Sender: TObject);
01 begin
02  Venda.IDVenda := 1;
03  Venda.Data    := Now;
04
05  Venda.ListaVendaItem.Clear;
06  MmDadosVenda.Clear;
07
08  Venda.AdicionarVendaItem('Sony Vaio XR8472');
09  Venda.AdicionarVendaItem('Dell Vostro');
10  Venda.AdicionarVendaItem('HP I7');
11
12  BtnListarVenda.Enabled    := True;
13  BtnCadastrarVenda.Enabled := False;
14 end;

Linhas 2 e 3: Preenchemos as propriedades do objeto Venda;

Linhas 5 e 6: Limpamos a coleção de itens da venda e o memo;

Linhas 8 a 10: Adicionamos três itens à venda;

Linhas 12 e 13: Habilitamos o botão de listar dados da venda e desabilitamos o botão de cadastrar nova venda.

Na sequência utilizamos o botão “Listar Venda” para imprimir no memo os dados da venda e seus itens. Para isso alteramos seu evento OnClick, da seguinte forma:

01 procedure TFrmListaObjetos.BtnListarVendaClick(Sender: TObject);
02 var
03  VendaItem: TVendaItem;
04 begin
05  MmDadosVenda.Lines.Add('IDVenda: ' + IntToStr(Venda.IDVenda));
06  MmDadosVenda.Lines.Add('Data: ' + FormatDateTime('dd/mm/yyyy', Venda.Data));
07
08  MmDadosVenda.Lines.Add('');
09  MmDadosVenda.Lines.Add('<<Lista de produtos>>');
10
11  for VendaItem in Venda.ListaVendaItem do
12    MmDadosVenda.Lines.Add('Produto : ' + VendaItem.Produto);
13
14  BtnListarVenda.Enabled    := False;
15  BtnCadastrarVenda.Enabled := True;
16 end;

Linhas 5 e 6: Adicionamos ao memo os dados do id e data da venda;

Linhas 8 e 9: Adicionamos ao memo duas linhas para separar os dados da venda dos seus itens;

Linhas 11 e 12: Percorremos os itens da venda usando um laço for e imprimimos no memo os dados de cada um;

Linhas 14 e 15: Desabilitamos o botão de listagem e habilitamos o botão de cadastrar venda.

Por fim, no método Destroy do form liberamos o objeto Venda da memória:

procedure TFrmListaObjetos.FormDestroy(Sender: TObject);
  begin
   FreeAndNil(Venda);
  end;