Diretivas de Parâmetros e Métodos
Autor: Geek on the beach | Publicado: 2012-02-18T14:34:00-02:00



Introdução



Dando continuidade ao básico de orientação a objetos em
Delphi, neste tópico abordaremos outros aspectos que servem de base para OO,
como diretivas de parâmetros var, out e const e as clausulas inherited,
overload, class function e class procedure.



Diretivas de parâmetros



A passagem de parâmetros para uma função ou procedimento
pode ser acompanhada de uma diretiva, essa diretiva é responsável por um
comportamento diferenciado do que estamos acostumados, muitas vezes isso
facilita a resolução de problemas e em outros casos é estritamente necessário,
sem elas não poderíamos desenvolver soluções para problemas que utilizam
recursão.

Cara diretiva, var,out e const, possui uma propriedade que a
torna única, é notório que a utilização de qualquer uma delas seja feita de
forma consciente, um var em um parâmetro, pode acelerar a execução de um código
ou torna-lo inútil.



Passagem por referência var



No geral, seja no Delphi ou qualquer outra linguagem, a
passagem de parâmetros é feita por meio de uma copia do valor, isso significa
que determinado valor sofre uma copia para outra área de memória completamente
diferente, isso torna possível a manipulação desses valores sem alterar o valor
original passado a procedure ou function, isso é extremamente útil na maioria
das vezes, porém, em alguns casos se faz necessário que a alteração sofrida
internamente no valor passado ao método, seja refletida fora do método, para
isso utilizamos a diretiva var, exemplo abaixo.



&nbsp;&nbsp; procedure CalculaImposto(var AValorTotal: Currency);



Existe uma diferença significativa em desempenho em relação
a passagem de parâmetros de forma convencional e a passagem utilizando a
diretiva var, isso acontece porque no meio convencional existe um custo de
processamento despendido para a copia do valor passado para outra area de
memoria, esse custo tende a ser infimo na maioria dos casos, mas em casos onde
a variavel de armazenamento tiver um valor relativamente grande em Bytes, esse
custo tende a ser muito alto. Outra questão que devemos lembrar é que apesar de
infimo esse custo de copia nunca deixa de existir, logo, comandos de repetição
tendem a mutiplicar esse custo infimo N vezes, o que dependendo do caso torna perceptível
a diferença de desempenho, essas diferenças são mais faceis de serem reparadas
em aplicações que exigem alto grau de processamento ou uma resposta muito
rapida, onde passamos a considerar milissegundos de processamento, um exemplo de
uma aplicação dessas são as que utilizam imagens, como o envio de stream em
tempo real, 200ms de atraso é algo significativo já que em tese um segundo deve
ser preenchido com algo em torno de 24 quadros, o que nos leva a um tempo de processamento
de aproximadamente 41,66ms por imagem a ser processada.



A diretiva var, na realidade é uma facilitação de um
ponteiro, todo o valor modificado dentro do método em&nbsp; questão é automaticamente refletido na variável
que representa aquele determinado espaço de memória fora do método ex.



Primeiro caso:



procedure CalculaImposto(var AValorTotal: Currency);

begin

&nbsp;&nbsp; AValorTotal
:= 100;

end;



Segundo caso:



procedure CalculaImposto(AValorTotal: Currency);

begin

&nbsp;&nbsp; AValorTotal
:= 100;

end;



Chamada da procedure



var

&nbsp;&nbsp; Total:
Currency;

begin 

&nbsp;&nbsp; Total := 200;

&nbsp;&nbsp; CalculaImposto(Total);

&nbsp;&nbsp; ShowMessage(CurrToStr(Total));

end; 



No primeiro caso o valor exigido no ShowMessage seria
"100", enquanto no segundo caso o valor exibido seria
"200", como podemos reparar as alterações sofridas dentro do método
CalculaImposto refletiram na variável Total (Magia negra?), isso ocorre porque
quanto declaramos uma variável isso nada mas é do que uma identificação
"bonitinha" para uma endereço de memória, logo quando passamos uma variável
por var (Ponteiro), estamos passando na realidade o mesmo endereço de memória
da variável original, logo se o endereço é o mesmo,
qualquer alteração de memória dentro de qualquer procedimento ira alterar o
valor de nossa variável externa.



Repare uma coisa importante, quando utilizamos var, somos
obrigados a passar uma variavel declarada, valores passados de forma constante
não são aceitos neste caso pois estamos lidando com endereços de memórias,
exemplo abaixo.



metodo

&nbsp;&nbsp; CalculaImposto(var
ATotal: Currency);



chamada (Errada)



&nbsp;&nbsp; CalculaImposto(100); // Este tipo de chamada não é permitida



chamada (Correta)



var

&nbsp;&nbsp; Total:
Currency;

begin

&nbsp;&nbsp; Total
:= 100;

&nbsp;&nbsp; CalculaImposto(Total);

end;



Cuidados com o var



A diretiva var é uma mão na roda, mas devemos tomar alguns
cuidados. Apesar dela diminuir o tempo de execução do código pois não existe o
custo de copia, ela é uma das diretivas mais perigosas de se usar, isso
acontece pois como as alteração são refletidas externamente, qualquer alteração
que a variável sofra mesmo que para otimizar a rotina ou tornar mais legível as
coisas, podem literalmente destruir o resto da execução do seu código (Meio difícil
enxergar um exemplo real?). Vou exemplificar aqui um caso muito comum que acontece,
passamos como var uma variável do tipo lista (TStringList ou TList por exemplo)
ou string, e para facilitar a múltipla pesquisa de valores dentro da string ou
lista, ordenamos seus valores de forma crescente, pois bem, se a continuação de
sua rotina fora do procedimento não entender está ordenação ou necessitar da
posição original dos valores, o resto da rotina está comprometida.



Const



Como o próprio nome sugere e como já é utilizado dentro do próprio
Delphi, const serve para determinar que a variável passada por parâmetro não
sofrera alterações dentro da rotina, isso parece meio sem lógica de inicio, mas
na realidade é uma ótima ferramenta, pois protege você de si mesmo, e de outros
programadores. A diretiva const garante que qualquer que seja o local em que eu
acesse a variável do parâmetro eu tenha certeza que o valor dela não sofreu
nenhuma alteração, a sintaxe de utilização do const é assim:



&nbsp;&nbsp; procedure CalculaImposto(const AValorTotal: Currency);



Out



Muito semelhante a var, a única diferença da diretiva out, é
que o valor inicial passado como parâmetro é ignorado, mas afinal para que eu
vou querer isso? A principal funcionalidade do out é a possibilidade de se
retornar mais de um valor por função, afinal, você pode passar quantas variáveis
quiser utilizando out, alias, o var também serve para a mesma coisa, mas quando
utilizamos out já sinalizamos que a função ou procedimento independente do valor
passado naquele parâmetro não ira utiliza-lo (Logo podemos passar lixo de
memoria, sem se preocupar com a inicialização de variáveis ou se isso pode
afetar nossa rotina), e que na realidade o que importa é o valor de retorno,
isso evita confusões quanto a passagem de parâmetro, utilize out sempre que for
necessário que uma função retorne mais de um valor.



A sintaxe de utilização do out é essa:



&nbsp;&nbsp; procedure CalculaImposto(out AValorTotal: Currency);



Como eu falei anteriormente, o valor inicial é descartado,
logo mesmo se eu fizer isto:



var&nbsp;&nbsp; Total: Currency;

begin

&nbsp;&nbsp; Total := 200;

&nbsp;&nbsp; CalculaImposto(Total);

end;



Dentro do procedimento calcula imposto o valor inicial
"200" será ignorado, assim que utilizamos out a variável assume o
valor default de seu tipo, se for uma por ex. string ficará vazia (Em tese).

Parâmetros com valor Default



Uma das possibilidades que possuímos ao construir um
procedimento ou função é atribuir um valor pré definido para um determinado parâmetro,
isso faz com que a atribuição de um novo valor a esse parâmetro seja opcional,
mas existem algumas ressalvas, os parâmetros que possuírem um valor pré
definido (Default) devem ser sempre agrupados o mais a direita possível dos parâmetros
que caracterizam o método, não é possivel utilizar esse tipo de passagem de
valores com parâmetros pré definidos misturados com outros obrigatórios,
vejamos alguns casos.



Sintaxe



&nbsp;&nbsp; procedure CalculaImposto(AValorImposto: Currency = 200);



Mas e se tivermos 2 (Dois) ou mais parâmetros?



&nbsp;&nbsp; procedure CalculaImposto(AValorImposto: Currency = 200;
AImposto: Boolean; ANomeImposto: string = '');



Essa forma é errada, pelo simples motivo que o compilador
não ira conseguir definir se na chamada da procedure CalculaImposto os parâmetros
passados estão errados levando em consideração sua "tipagem" ou se
devem ser ignorados devido ao valor default.



Exemplo:



&nbsp;&nbsp; CalculaImposto(True,'ISS');



Nesse caso o Delphi acusara erro, devido o que foi falado
acima, o certo então é declarar os parâmetros com valores pré definidos
agrupados a direita.



&nbsp;&nbsp; procedure CalculaImposto(AImposto: Boolean; AValorImposto: Currency = 200; ANomeImposto: string = '');



Assim nossa chamada de função fica dessa forma:



&nbsp;&nbsp; CalculaImposto(True);



E os parâmetros AValorImposto e ANomeImposto, recebem
respectivamente o valor "default" atribuídos a eles.



Class
procedure e Class Function



Quando criamos uma classe e programamos alguns métodos, é notório
que para utilizar estes métodos seja necessário instanciar a classe em questão,
se isso não ocorrer, nos teremos o fatídico erro "Acess Violation at
Adress xxxxx", acontece que a class procedure e class function servem
justamente para isso, elas anulam a necessidade de se instanciar a classe para
a utilização desses métodos, assim, nós podemos chamar o(s) método(s)
diretamente sem instanciar a classe através do create.



Primeiro modo (Sem class)



var

&nbsp;&nbsp; Objeto:
TPessoa;

begin

&nbsp;&nbsp; Objeto := TPessoa.Create;

&nbsp;&nbsp; if not
Objeto.ValidaCPF("CPF em questão") then

&nbsp;&nbsp; &nbsp;&nbsp; ShowMessage('CPF
Inválido');

&nbsp;&nbsp; Objeto.Free;

end;



Segundo modo (Com class)



begin

&nbsp;&nbsp; if not
TPessoa.ValidaCPF("CPF em questão") then

&nbsp;&nbsp; &nbsp;&nbsp; ShowMessage('CPF
Inválido');

end;



É notável a diferença entre um e outro, em outras linguagens
esses métodos são conhecidos como statics, é um pouco difícil reparar a
utilidade disso logo a primeira vista, isso acontece pois o Delphi deriva
do pascal, possuindo assim procedures e functions separadas em units, o que
elimina de certa forma a utilização de metodos statics em boa parte dos casos,
mas não todos, a questão é que o metodo static trás duas principais vantagens:



&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1-
Organização dos métodos por classes e não por units

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2- A
Certeza de onde o método veio ao utiliza-lo



Quando utilizamos a divisão por units (Não que seja ruim),
temos um pequeno problema que torna-se gigante ao passar do tempo, boa parte
das procedures e functions, ficam perdidas dentro das units, chega um ponto
onde não sabemos mais de onde veio aquela rotina (Vamos pro POG! Declare todas
as units no uses uma deve ter a rotina em questão).



A utilização de class procedure e function trás algumas
peculiaridades em relação a utilização da classe, a primeira de todas e mais
obvia, é que não é necessário instanciar a classe em questão, a segunda é que
os métodos statics (class) não podem acessar variáveis de dentro da classe declaradas
no Private,Public etc..., isso acontece pois não existe garantia que a classe
está instanciada, dá mesma forma métodos class não podem utilizar-se de métodos
não class dentro da classe, o contrario é possível, essas restrições garantem
que não ocorrera nenhum "Acess Violation" ao utilizar métodos
statics.



Exemplo



TPessoa =
class

private

&nbsp;&nbsp; FNome: string;

public

&nbsp;&nbsp; class function ValidaCPF(ACPF:
string): Boolean;

end;



class
function TPessoa.ValidaCPF(ACPF: string): Boolean;

begin

&nbsp;&nbsp; //
Neste ponto a variavel FNome não é acessível, ela só seria acessível se fosse
uma constante ou se fosse declarada como class var

end;



Overload



Olverload, significa sobrecarga, e como o nome já diz, nos
vamos sobrecarregar um método, o overload é utilizada quando temos a
necessidade de ter dois métodos diferentes com o mesmo nome, porém, os parâmetros
passados para esses métodos devem ser diferentes, pra falar a verdade, o
compilador reconhece qual método é qual, através da lista de parâmetros.



TPessoa =
class

private

public

&nbsp;&nbsp; constructor Create; overload;

&nbsp;&nbsp; constructor Create(ANome:
string); overload;

end;



Nesse caso, sobrecarregamos o método construtor da classe,
mas como podemos ver a lista de parâmetros é diferente, o primeiro não pede parâmetro
algum, enquanto o segundo exige o nome da pessoa, para o compilador pouco
importa o nome da variável, o que importa na diferenciação do método
sobrecarregado são &nbsp;os tipos e posição
dos parâmetros da lista de parâmetros, isso implica que, mesmo se o nome dos parâmetros
forem diferentes se os tipos forem iguais e suas posições relativas forem
iguais em ambos os métodos, não existe como diferenciar um método do outro,
logo, ocorrera um erro de compilação.



Veja o exemplo abaixo.



TPessoa = class

private

public

&nbsp;&nbsp; constructor Create(ACNPJ: string);
overload;

&nbsp;&nbsp; constructor Create(ACPF: string); overload;

end;



Isso não é aceito, pois não é possível definir qual tipo de
metodo está sendo invocado mesmo o nome do parâmetro sendo diferente.



Inherited



Inherited é utilizado para chamar funções de classes pai a
partir de classes filhas, essas chamadas porem ocorrer em qualquer método da
classe filha contando que este não seja um método static (class procedure ou
function), não existem segredos ao se utilizar inherited para chamadas de metodos
mais altos, sua chamada pode ser feita passando o nome do método ou não, de
acordo com o escopo da chamada, isto é, se existe um mesmo método na classe pai
que foi sobre-escrito na classe filha, não é necessário utilizar inherited
seguido do nome do metodo.



TPessoa = class

private

public

&nbsp;&nbsp; destructor Destroy; override;

end;



destructor
TPessoa.Destroy;

begin

&nbsp;&nbsp; ShowMessage('Estou sendo
destruido');

&nbsp;&nbsp; inherited; // Também
podemos utilizar inherited Destroy;

end;



Métodos que exigem passagem de parâmetros devem ser
chamados&nbsp; da seguinte forma:



&nbsp;&nbsp; inherited
CalcularImposto(Valor);



Também podemos utilizar isso para chamar um Create de classe
mais alta, antes ou depois de executar o create da classe filha desta forma:



constructor
Create(ANome: string; APessoaFisica: Boolean);

begin

&nbsp;&nbsp; ShowMessage(ANome);

&nbsp;&nbsp; inherited
Create(APessoaFisica);

end;



ou ainda



constructor
Create(ANome: string; APessoaFisica: Boolean);

begin

&nbsp;&nbsp; inherited
Create(APessoaFisica);

&nbsp;&nbsp; ShowMessage(ANome);

end;



Repare que quando eu executo inherited
Create(APessoaFisica);, estou chamando o método Create da classe pai, que no caso
espera um parâmetro booleano, que de acordo com o nosso escopo define se é uma
pessoa física ou jurídica.



Ficamos por aqui nesse tópico, nos próximos abordarei a
destruição de classes e algumas questões como a utilização de memória da
aplicação e desempenho de metodos, esses são assuntos primordiais antes de
avançarmos para heranças, classes agregadas e interface.



Até a próxima.