Introdução à definição de propriedades em classes
Introdução à definição de propriedades em classes, neste artigo de Daniel Wildt. Acesso Liberado.
Artigos Delphi Introdução à definição de propriedades em classes
Nesta dica irei apresentar o assunto propriedades, mostrando como você pode fazer para unificar a forma de acesso aos serviços que você disponibiliza em suas classes.
Como exemplo da nossa dica, iremos trabalhar com uma aplicação baseada no seguinte: (1) uma tela (form) para entrada de dados de clientes e (2) uma tela (form) que vai simular a classe de clientes, que utilizaremos para visualização de dados. A interface pode ser vista na figura 1:
A nossa classe de clientes tem a seguinte interface:
TFormCliente = class(TForm)
lblNome: TLabel;
lblEndereco: TLabel;
lblTelefone: TLabel;
lblValorNome: TLabel;
lblValorEndereco: TLabel;
lblValorTelefone: TLabel;
Então para atualizar a tela de clientes, temos que acessar os componentes visuais de forma direta. Veja a implementação do evento onClick do botão Set da tela principal:
procedure TFormMain.btnSetClick(Sender: TObject);
FormCliente.lblValorNome.Caption := edtNome.Text;
FormCliente.lblValorEndereco.Caption := edtEndereco.Text;
FormCliente.lblValorTelefone.Caption := edtTelefone.Text;
// Limpar conteúdo dos Edits.
edtNome.Clear;
edtEndereco.Clear;
edtTelefone.Clear;
Para fazer a atualização da informação do nome estamos acessando a propriedade caption do Label existente na tela de cliente. Imagine que ao atualizar o nome do cliente, temos que atualizar também o caption do form cliente. Aí podemos adicionar a seguinte linha:
FormCliente.Caption := edtNome.Text;
O que parece isto? Nós queremos atualizar o nome do cliente. Porque deve ser uma tarefa do formulário principal ficar atualizando todos os componentes visuais de uma outra tela? O form principal quer avisar para o FormCliente mudar o atributo Nome. O processo de atualizar os dados, deve ser tarefa do Form Cliente, uma tarefa interna. Até porque a interface visual da tela pode mudar, mas isto não pode ser motivo para ter que modificar todo o sistema.
Temos que buscar um baixo acoplamento entre os objetos. Do jeito que está não temos reutilização de código, pois toda vez que alguém quiser utilizar o FormCliente, vai ter que implementar as funcionalidades para atribuição do Nome. Estamos gerando um alto custo para manutenções. Vamos então a primeira transformação do nosso Fonte.
2. Processo padrão para encapsulação de informações
O objetivo aqui será criar métodos de acesso dentro do form cliente. O que são os métodos de acesso? Qual a utilidade?
Bom, queremos criar um serviço. Queremos disponibilizar uma forma padrão para que outras classes possam utilizar a nossa classe de cliente, podendo atualizar e obter informações dela, sem depender de objetos de interface.
Iremos criar métodos Set (atribuição) e métodos Get (obter valor) para cada atributo da nossa classe cliente.
Vamos ver a nova definição da interface.
procedure SetNome(const Value : string);
procedure SetEndereco(const Value : string);
procedure SetTelefone(const Value : string);
function GetNome : string;
function GetEndereco : string;
function GetTelefone : string;
Feito isto, agora temos que modificar o método para atribuir informações da tela principal. Esta é a nova implementação do processo.
FormCliente.SetNome(edtNome.Text);
FormCliente.SetEndereco(edtEndereco.Text);
FormCliente.SetTelefone(edtTelefone.Text);
Não estamos mais atualizando duas informações da tela de cliente quando modificamos o nome. Isto quer dizer que o método SetNome possui mais do que uma simples atribuição do parâmetro passado para o label de valor. Vamos ver esta implementação:
procedure TFormCliente.SetNome(const Value: string);
lblValorNome.Caption := Value;
Self.Caption := Value; // atualiza o caption do form
Com isto estamos abstraindo do usuário da classe TFormCliente o que é atualizado quando é chamado o método SetNome. Outro processo interessante no uso de métodos de acesso é que podemos criar formas de validação das informações. Por exemplo, o nome não pode ser vazio. Se o parâmetro passado for inválido, podemos levantar uma exceção com o problema. Nova implementação do SetNome:
if (Trim(Value) = '') then
raise Exception.Create('Nome não pode ser vazio!')
Self.Caption := Value;
Ok, conseguimos modificar o acesso a informação do nome através dos métodos de acesso. Para obter a informação utilizamos GetNome, para atribuir um novo valor utilizamos SetNome passando por parâmetro o novo valor. Ainda, incluímos validações ao atribuir novo valor para o Nome, o que melhora a qualidade da implementação.
Seria interessante que pudéssemos acessar o Nome como um serviço, mas do jeito que está estamos utilizando dois métodos para acessar a mesma informação. Agora vamos modificar este processo. Primeiro, vamos aprender um conceito chamado "Princípio da Referência Uniforme".
3. Princípio da referencia Uniforme
Segundo Bertrand Meyer, que foi entre outras coisas autor da linguagem Eiffel, este princípio indica que:
"Os serviços oferecidos por um módulo devem estar disponíveis através de uma notação uniforme, independente do serviço ser implementado através de armazenamento de informações ou através de processos de cálculo."
Quando se fala em armazenamento, falamos de atributos de nossas classes, que armazenam informações que formam o estado dos objetos. E processo de cálculo, é uma função ou procedimento que retorna valor ou atualiza o estado de objetos ao executar.
Isto quer dizer que devemos acessar procedimentos e atributos da mesma forma. Seria como poder utilizar uma variável Nome e ao atribuir um valor, que ocorresse o mesmo que ocorre na chamada do método SetNome. Como fazer isto acontecer? Através de propriedades! Próximo passo!
4. Definindo uma propriedade
Vou definir uma propriedade "Teste", para poder explicar o funcionamento do processo. Se posicione na seção public da classe TFormCliente e declare "property Teste : string;". Após isto, pressione Ctrl+Shift+C para chamar o recurso de CodeCompletion do Delphi.
O nosso fonte vai ter algumas linhas novas, vamos ver:
FTeste: string;
procedure SetTeste(const Value: string);
property Teste : string read FTeste write SetTeste;
Automaticamente foi criado um atributo FTeste e um método SetTeste para trabalhar com a propriedade.
Foi criada um atributo chamado FTeste, para encapsular a informação. Foi declarado como um atributo privado, o que significa que pode ser acessado apenas dentro da classe TFormCliente. O TFormPrincipal não tem acesso a esta informação.
Foi criado um método de acesso chamado SetTeste, para receber a atualização da informação Teste. Note que o método também foi declarado como privado. Nós não vamos utilizar o método SetTeste para atribuir valor para FTeste, vamos utilizar a propriedade.
Lendo a declaração da propriedade temos: propriedade Teste, do tipo String. Quando se precisar obter o valor da propriedade, se consulta o valor da variável FTeste e quando se precisar atribuir um valor para a propriedade, o método SetTeste é chamado. O método SetTeste atualiza o valor do atributo FTeste.
Na definição do "read", podemos indicar um atributo do mesmo tipo, ou podemos indicar um método que retorne um valor do tipo da propriedade. Desta forma podemos criar propriedades calculadas. Da mesma forma, na definição de "write" podemos indicar um atributo ou então um método que recebe um parâmetro do tipo da propriedade. Ainda, uma propriedade pode ter apenas a definição "read", no caso de uma propriedade somente leitura, assim como pode ter apenas a definição "write".
Para ler a informação Teste na classe TFormPrincipal, podemos codificar algo como:
ShowMessage(FormCliente.Teste);
// Vai acessar o valor da variável FTeste
E para atribuir um valor a propriedade:
FormCliente.Teste := 'Novo valor'; // Chama método SetTeste
Quer dizer voltamos a falar com variáveis, mas variáveis inteligentes, pois podem executar validações e levantando exceções caso seja necessário.
Feito, isto, agora temos que fazer dois passos para completar o nosso processo:
1) Criar propriedades para os nossos atributos da classe TFormCliente. Vamos criar propriedades Nome, Endereco e Telefone. A declaração fica assim:
property Nome : string read GetNome write SetNome;
property Endereco : string read GetEndereco write SetEndereco;
property Telefone : string read GetTelefone write SetTelefone;
2) Os métodos de acesso Get e Set devem ser passados para a seção private da classe. Assim apenas o que é público vai poder ser acessado por quem usa a classe TFormCliente. A interface da classe TFormCliente fica assim:
procedure SetTelefone(const Value: string);
function GetEndereco: string;
function GetTelefone: string;
E voltando ao processo de atribuições pela classe TFormPrincipal, teremos o seguinte, para atualizar informações na classe TFormCliente:
FormCliente.Nome := edtNome.Text;
FormCliente.Endereco := edtEndereco.Text;
FormCliente.Telefone := edtTelefone.Text;
E tudo continua funcionando. Se atribuirmos '' (vazio), para o campo nome, apesar de ser uma atribuição, como se fosse uma variável comum, estamos na verdade executando o método SetNome, que vai levantar exceção caso o parâmetro seja inválido.
Verificamos nesta dica uma introdução ao uso de propriedades com o Delphi. As propriedades podem nos ajudar a criar abstrações a padronizar a forma de acesso a informações.
Próximos passos, estude os três exemplos que foram montados nesta dica, cada um com as suas evoluções e tente aplicar estes conceitos em seus sistemas. Além disto, você pode procurar o uso de propriedades na criação de componentes, onde o uso de propriedades é essencial!