O artigo trata de interfaces, uma maneira de se estabelecer um “contrato” entre duas entidades, sejam eles objetos, componentes ou outros programas, de modo que possam se comunicar através de trocas de mensagens que nada mais são do que chamadas de métodos.
Em que situação o tema é útil
Sempre que for necessário escrever alguma classe que siga um comportamento padrão sem que necessariamente seja de uma linhagem específica. Com interfaces é possível, por exemplo, escrever um método que aceite um objeto que seja de uma interface, e que outro programador que escreverá e instanciará este objeto. Qualquer tipo de objeto pode ser passado contanto que implemente esta interface. Outra possibilidade é especificar um comportamento padrão, um conjunto de métodos, sem se preocupar em como eles serão implementados.
Aprofundando em Interfaces
Se você já programa orientado a objetos a algum tempo, está familiarizado com interfaces. Para quem já viu o .Net framework ou o Java, o uso que se faz de interfaces nesses ambientes é enorme. Um exemplo disso é que no .Net framework para se criar um objeto do tipo “coleção” basta implementar certas interfaces não importando como a coleção de objetos é implementada internamente, se é vetor, lista ligada ou outro tipo de coleção. A mesma coisa vale para tipos lista, command, connection e assim por diante. No Delphi também podemos fazer isso: criando vários objetos que implementem a mesma interface dá a possibilidade de armazenar qualquer um deles numa variável do tipo dessa interface.
No artigo anterior, Interfaces – Teoria e Prática, foi explorada a teoria sobre interfaces. Foram apresentadas características das mesmas, como declará-las e usá-las em Delphi e ainda foi mostrado sobre as GUIDs.
Também foi apresentada a agregação, mostrando como um objeto pode implementar várias interfaces simulando herança múltipla ou como um objeto pode implementar uma interface e conter uma referência para outro que implemente outra interface. Foram feitas algumas experiências com veículos e animais. Outro ponto interessante para ser mostrado sobre as interfaces é a contagem de referências. Nesteartigo daremos continuidade ao assunto explorando sobre como utilizar herança com interfaces e como manter referências de forma correta.
Interfaces com propriedades
Considere-se a interface ICliente da Listagem 1.
Listagem 1. Interface ICliente
unit uICliente;
interface
type
ICliente = interface
['{3A192476-2CF2-4895-8B97-7DED7A8EC181}']
procedure SetNome(const vNome: string);
procedure SetCPF(const vCPF: string);
procedure SetDtNasc(const vDtNasc: TDateTime);
procedure SetEndereco(const vEndereco: string);
function GetNome: string;
function GetCPF: string;
function GetDtNasc: TDateTime;
function GetEndereco: string;
property Nome: string read GetNome write SetNome;
property CPF: string read GetCPF write SetCPF;
property DtNasc: TDateTime read GetDtNasc write SetDtNasc;
property Endereco: string read GetEndereco write SetEndereco;
end;
implementation
end.
Interfaces podem ter propriedades, contanto que elas estejam configuradas com métodos acessores que também façam parte da interface. Como consequência, os métodos acessores deverão ser públicos na implementação da interface, pois todos os métodos de uma interface devem ser implementados como públicos.
Isso não é uma consequência ruim. Os métodos acessores públicos podem ser usados de igual forma ao Java ou C++: invocando o acessor Set para mudar o valor de um atributo ou o acessor Get para obter o valor do atributo. Mesmo assim, há vantagem de usar uma propriedade diretamente em vez dos assessores. Propriedades que escrevem ou leem seus valores diretamente nos campos privados podem constituir uma violação de encapsulamento. Entretanto, em alguns casos, permite-se a leitura direta e a escrita usando-se um método acessor. Cada caso é um caso e a própria VCL tem muitos exemplos de classes e componentes com propriedades que leem ou escrevem diretamente de campos privados.
Também não é nenhum crime expor os campos se eles forem realmente atributos relevantes da entidade que precisam ser vistos e modificados pelo ambiente externo, contanto que não haja uma maneira especial de se ler ou gravar esses atributos.
Sempre que houver uma maneira especial de se ler ou gravar esses atributos, como cálculos, validações ou máscaras, sempre é possível encapsular este atributo com métodos acessores e vincular esses acessores a uma propriedade, permanecendo o acesso ao atributo transparente.
Métodos acessores são métodos usados para se ler ou modificar o valor de um atributo, sendo que o atributo pode permanecer privado e ser exposto através de métodos. É uma forma de encapsulamento do atributo/propriedade.
Geralmente métodos acessores para se ler um atributo começam com “Get” e para modificar o atributo começam com Set.
Em linguagens como Java e C++ é muito comum ter-se vários atributos privados e um método Get ou Set para cada um que necessitar ser publicado ou exposto para o ambiente externo.
Outras linguagens, como o Delphi, permitem que métodos acessores (privados ou públicos) sejam automaticamente ativados por propriedades.
A vantagem disso é que propriedades podem ser usadas como variáveis, estando de qualquer um dos lados de uma atribuição ou expressão, usadas com operadores +, -, /, := e assim por diante, coisa que não dá para ser feita com métodos sem o uso de parâmetros ou que estes estejam à direita das expressões.
Adicionalmente, linguagens como C# no .Net Framework criam em linguagem intermediária métodos acessores automaticamente se estes não forem especificados no escopo de uma propriedade.
Implementação de interfaces
A interface ICliente da Listagem 1 contém uma propriedade chamada CPF dando a entender que o cliente sempre será uma pessoa física. Isso poderia ser feito de outra forma, a propriedade poderia se chamar Documento e classes diferentes de pessoa física ou jurídica implementariam esta mesma interface. Uma classe que pode implementar a interface ICliente é a classe TCliente da Listagem 2.
Listagem 2. Classe TCliente
type
TCliente = class(TInterfacedObject, ICliente)
private
FNome: string;
FEndereco: string;
FCPF: string;
FDtNasc: TDateTime;
public
procedure SetNome(const vNome: string);
procedure SetCPF(const vCPF: string);
procedure SetDtNasc(const vDtNasc: TDateTime);
procedure SetEndereco(const vEndereco: string);
function GetNome: string;
function GetCPF: string;
function GetDtNasc: TDateTime;
function GetEndereco: string;
property Nome: string read GetNome write SetNome;
property CPF: string read GetCPF write SetCPF;
property DtNasc: TDateTime read GetDtNasc write SetDtNasc;
property Endereco: string read GetEndereco write SetEndereco;
end;
O objetivo de se implementar clientes através de interfaces é lidar com tipos diferentes de clientes. Não só o clássico “pessoa física” e “pessoa jurídica” mas clientes especiais como “cinco estrelas”, “mensalistas”, “diaristas” e assim por diante. Essa categorização pode variar de acordo com sua regra de negócio. Imaginando que um cliente, além de ser pessoa física ou jurídica, pode ser estrangeiro, ao utilizar listas genéricas de clientes podemos ter um parâmetro que define o tipo de cliente que elas usam.
Interfaces com Generics
Interfaces também podem ter parâmetros genéricos ou servir como parâmetros desse tipo para outra classe. Isso acrescenta dois níveis de abstração no modelo, pois além do uso de interfaces permitir que várias classes a implementem de uma forma diferente, o uso de um parâmetro genérico faz com que se crie várias interfaces baseadas na mesma. Uma interface genérica é declarada de forma muito similar a não genérica, como mostra o código a seguir.
IInterfaceGenerica<T> = interface
E para que classes possam implementar essa interface, elas devem informar qual o tipo do parâmetro genérico T, como é visto a seguir.
TClasseGenericaInteiro = class(TInterfacedObject, IInterfaceGenerica<Integer>)
public
function Soma(a, b: Integer): Integer;
function Subtracao(a, b: Integer): Integer;
end;
Para entender o uso dos parâmetros genéricos vamos analisar a interface IInterfaceGenerica<T>. Essa interface expõe alguns métodos que representam operações matemáticas. No caso da classe TClasseGenericaInteiro essas operações são realizadas com números inteiros. Isso é definido ao passar na declaração da classe o tipo Integer. Outras classes foram criadas empregando outros tipos, como String e Extended, ou seja, a mesma operação matemática só que envolvendo tipos diferentes. Imaginando um uso dessas classes, teríamos um formulário que instanciaria cada classe dessas e lançaria em um memo o resultado de suas operações matemáticasenvolvendo os diversos tipos, exatamente como mostra a ...